Unverified Commit 1af72c1c authored by Igor Demin's avatar Igor Demin Committed by GitHub

DirectXOffscreenContext to allow drawing into offscreen texture (#1016)

Needed to run benchmarks without vsync interference.

The code extracted from Direct3DSwingRedrawer with minimal changes.

- implementation for other API will be added when it is needed (Metal is
considered later)
- there are no plans to make commonMain abstraction in `skiko` as it is
not needed at the moment

## Testing
- manually in benchmarks with this code:
```
@OptIn(ExperimentalSkikoApi::class)
class DirectXGraphicsContext() : DesktopGraphicsContext {
    private val context = DirectXOffscreenContext()
    private var texture: DirectXOffscreenContext.Texture? = null

    override fun surface(width: Int, height: Int): Surface {
        texture?.close()
        texture = context.Texture(width, height)
        return Surface.makeFromBackendRenderTarget(
            context.directContext,
            texture!!.backendRenderTarget,
            SurfaceOrigin.TOP_LEFT,
            SurfaceColorFormat.BGRA_8888,
            ColorSpace.sRGB,
            SurfaceProps(pixelGeometry = PixelGeometry.UNKNOWN)
        ) ?: throw IllegalStateException("Can't create Surface")
    }

    override suspend fun awaitGPUCompletion() {
        texture?.waitForCompletion()
    }

    override fun close() {
        texture?.close()
        context.close()
    }
}

interface DesktopGraphicsContext : GraphicsContext, AutoCloseable
```
- new test for SkiaSwingLayer
- new test for DirectXOffscreenContext
parent 84bff4d9
...@@ -150,14 +150,14 @@ extern "C" ...@@ -150,14 +150,14 @@ extern "C"
std::string name(tmp.begin(), tmp.end()); std::string name(tmp.begin(), tmp.end());
jstring jname = env->NewStringUTF(name.c_str()); jstring jname = env->NewStringUTF(name.c_str());
static jclass cls = (jclass) env->NewGlobalRef(env->FindClass("org/jetbrains/skiko/swing/Direct3DSwingRedrawer")); static jclass cls = (jclass) env->NewGlobalRef(env->FindClass("org/jetbrains/skiko/graphicapi/InternalDirectXApi"));
static jmethodID method = env->GetMethodID(cls, "isAdapterSupported", "(Ljava/lang/String;)Z"); static jmethodID method = env->GetMethodID(cls, "isAdapterSupported", "(Ljava/lang/String;)Z");
return env->CallBooleanMethod(redrawer, method, jname); return env->CallBooleanMethod(redrawer, method, jname);
} }
// TODO: extract common code with directXRedrawer // TODO: extract common code with directXRedrawer
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_swing_Direct3DSwingRedrawer_chooseAdapter( JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_graphicapi_InternalDirectXApi_chooseAdapter(
JNIEnv *env, jobject redrawer, jint adapterPriority) { JNIEnv *env, jobject redrawer, jint adapterPriority) {
gr_cp<IDXGIFactory4> deviceFactory; gr_cp<IDXGIFactory4> deviceFactory;
if (!SUCCEEDED(CreateDXGIFactory1(IID_PPV_ARGS(&deviceFactory)))) { if (!SUCCEEDED(CreateDXGIFactory1(IID_PPV_ARGS(&deviceFactory)))) {
...@@ -187,7 +187,7 @@ extern "C" ...@@ -187,7 +187,7 @@ extern "C"
return 0; return 0;
} }
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_swing_Direct3DSwingRedrawer_createDirectXOffscreenDevice( JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_graphicapi_InternalDirectXApi_createDirectXOffscreenDevice(
JNIEnv *env, jobject redrawer, jlong adapterPtr) { JNIEnv *env, jobject redrawer, jlong adapterPtr) {
gr_cp<IDXGIFactory4> deviceFactory; gr_cp<IDXGIFactory4> deviceFactory;
...@@ -260,7 +260,7 @@ extern "C" ...@@ -260,7 +260,7 @@ extern "C"
return toJavaPointer(d3dDevice); return toJavaPointer(d3dDevice);
} }
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_swing_Direct3DSwingRedrawer_makeDirectXRenderTargetOffScreen( JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_graphicapi_InternalDirectXApi_makeDirectXRenderTargetOffScreen(
JNIEnv *env, jobject redrawer, jlong texturePtr) { JNIEnv *env, jobject redrawer, jlong texturePtr) {
DirectXOffScreenTexture *texture = fromJavaPointer<DirectXOffScreenTexture *>(texturePtr); DirectXOffScreenTexture *texture = fromJavaPointer<DirectXOffScreenTexture *>(texturePtr);
ID3D12Resource* resource = texture->resource; ID3D12Resource* resource = texture->resource;
...@@ -275,7 +275,7 @@ extern "C" ...@@ -275,7 +275,7 @@ extern "C"
return reinterpret_cast<jlong>(renderTarget); return reinterpret_cast<jlong>(renderTarget);
} }
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_swing_Direct3DSwingRedrawer_makeDirectXContext( JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_graphicapi_InternalDirectXApi_makeDirectXContext(
JNIEnv *env, jobject redrawer, jlong devicePtr) JNIEnv *env, jobject redrawer, jlong devicePtr)
{ {
DirectXOffscreenDevice *d3dDevice = fromJavaPointer<DirectXOffscreenDevice *>(devicePtr); DirectXOffscreenDevice *d3dDevice = fromJavaPointer<DirectXOffscreenDevice *>(devicePtr);
...@@ -283,7 +283,7 @@ extern "C" ...@@ -283,7 +283,7 @@ extern "C"
return toJavaPointer(GrDirectContext::MakeDirect3D(backendContext).release()); return toJavaPointer(GrDirectContext::MakeDirect3D(backendContext).release());
} }
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_swing_Direct3DSwingRedrawer_makeDirectXTexture( JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_graphicapi_InternalDirectXApi_makeDirectXTexture(
JNIEnv *env, jobject redrawer, jlong devicePtr, jlong oldTexturePtr, jint width, jint height) { JNIEnv *env, jobject redrawer, jlong devicePtr, jlong oldTexturePtr, jint width, jint height) {
DirectXOffscreenDevice *device = fromJavaPointer<DirectXOffscreenDevice *>(devicePtr); DirectXOffscreenDevice *device = fromJavaPointer<DirectXOffscreenDevice *>(devicePtr);
DirectXOffScreenTexture *oldTexture = fromJavaPointer<DirectXOffScreenTexture *>(oldTexturePtr); DirectXOffScreenTexture *oldTexture = fromJavaPointer<DirectXOffScreenTexture *>(oldTexturePtr);
...@@ -307,15 +307,14 @@ extern "C" ...@@ -307,15 +307,14 @@ extern "C"
return toJavaPointer(texture); return toJavaPointer(texture);
} }
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_swing_Direct3DSwingRedrawer_disposeDirectXTexture( JNIEXPORT void JNICALL Java_org_jetbrains_skiko_graphicapi_InternalDirectXApi_disposeDirectXTexture(
JNIEnv *env, jobject redrawer, jlong texturePtr) { JNIEnv *env, jobject redrawer, jlong texturePtr) {
DirectXOffScreenTexture *texture = fromJavaPointer<DirectXOffScreenTexture *>(texturePtr); DirectXOffScreenTexture *texture = fromJavaPointer<DirectXOffScreenTexture *>(texturePtr);
delete texture; delete texture;
} }
JNIEXPORT jboolean JNICALL Java_org_jetbrains_skiko_swing_Direct3DSwingRedrawer_readPixels( JNIEXPORT void JNICALL Java_org_jetbrains_skiko_graphicapi_InternalDirectXApi_waitForCompletion(
JNIEnv *env, jobject redrawer, jlong devicePtr, jlong texturePtr, jbyteArray byteArray) { JNIEnv *env, jobject redrawer, jlong devicePtr, jlong texturePtr) {
jbyte *bytesPtr = env->GetByteArrayElements(byteArray, nullptr);
DirectXOffscreenDevice *device = fromJavaPointer<DirectXOffscreenDevice *>(devicePtr); DirectXOffscreenDevice *device = fromJavaPointer<DirectXOffscreenDevice *>(devicePtr);
...@@ -373,6 +372,13 @@ extern "C" ...@@ -373,6 +372,13 @@ extern "C"
fence->SetEventOnCompletion(fenceValue, fenceEvent); fence->SetEventOnCompletion(fenceValue, fenceEvent);
WaitForSingleObject(fenceEvent, INFINITE); WaitForSingleObject(fenceEvent, INFINITE);
} }
}
JNIEXPORT jboolean JNICALL Java_org_jetbrains_skiko_graphicapi_InternalDirectXApi_readPixels(
JNIEnv *env, jobject redrawer, jlong texturePtr, jbyteArray byteArray) {
jbyte *bytesPtr = env->GetByteArrayElements(byteArray, nullptr);
DirectXOffScreenTexture *texture = fromJavaPointer<DirectXOffScreenTexture *>(texturePtr);
auto rangeLength = texture->readbackBufferWidth(); auto rangeLength = texture->readbackBufferWidth();
D3D12_RANGE readbackBufferRange{ 0, rangeLength }; D3D12_RANGE readbackBufferRange{ 0, rangeLength };
...@@ -408,13 +414,13 @@ extern "C" ...@@ -408,13 +414,13 @@ extern "C"
return true; return true;
} }
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_swing_Direct3DSwingRedrawer_disposeDevice( JNIEXPORT void JNICALL Java_org_jetbrains_skiko_graphicapi_InternalDirectXApi_disposeDevice(
JNIEnv *env, jobject redrawer, jlong devicePtr) { JNIEnv *env, jobject redrawer, jlong devicePtr) {
DirectXOffscreenDevice *device = fromJavaPointer<DirectXOffscreenDevice *>(devicePtr); DirectXOffscreenDevice *device = fromJavaPointer<DirectXOffscreenDevice *>(devicePtr);
delete device; delete device;
} }
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_swing_Direct3DSwingRedrawer_getAlignment( JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_graphicapi_InternalDirectXApi_getTextureAlignment(
JNIEnv *env, jobject redrawer) { JNIEnv *env, jobject redrawer) {
return D3D12_TEXTURE_DATA_PITCH_ALIGNMENT; return D3D12_TEXTURE_DATA_PITCH_ALIGNMENT;
} }
......
package org.jetbrains.skiko.graphicapi
import org.jetbrains.skia.BackendRenderTarget
import org.jetbrains.skia.DirectContext
import org.jetbrains.skiko.ExperimentalSkikoApi
import org.jetbrains.skiko.GpuPriority
import org.jetbrains.skiko.RenderException
import org.jetbrains.skiko.graphicapi.InternalDirectXApi.alignedTextureWidth
import org.jetbrains.skiko.graphicapi.InternalDirectXApi.chooseAdapter
import org.jetbrains.skiko.graphicapi.InternalDirectXApi.createDirectXOffscreenDevice
import org.jetbrains.skiko.graphicapi.InternalDirectXApi.disposeDevice
import org.jetbrains.skiko.graphicapi.InternalDirectXApi.disposeDirectXTexture
import org.jetbrains.skiko.graphicapi.InternalDirectXApi.makeDirectXContext
import org.jetbrains.skiko.graphicapi.InternalDirectXApi.makeDirectXRenderTargetOffScreen
import org.jetbrains.skiko.graphicapi.InternalDirectXApi.makeDirectXTexture
import org.jetbrains.skiko.graphicapi.InternalDirectXApi.readPixels
import org.jetbrains.skiko.graphicapi.InternalDirectXApi.waitForCompletion
/**
* Class that allows drawing into offscreen DirectX texture.
*
* Used in the `benchmarks` project:
* https://github.com/JetBrains/compose-multiplatform/blob/cedd48f99e877f8b936ff574812d573baf231307/benchmarks/multiplatform/benchmarks/src/commonMain/kotlin/MeasureComposable.kt#L16
*/
@ExperimentalSkikoApi
class DirectXOffscreenContext : AutoCloseable {
private val adapter = chooseAdapter(GpuPriority.Integrated)
private val device = createDirectXOffscreenDevice(adapter).also {
if (it == 0L) {
throw RenderException("Failed to create DirectX12 device.")
}
}
val directContext = DirectContext(makeDirectXContext(device))
override fun close() {
directContext.close()
disposeDevice(device)
}
inner class Texture(desiredWidth: Int, desiredHeight: Int) : AutoCloseable {
/**
* Aligned width/height that is needed for performance optimization,
* since DirectX uses aligned bytebuffer.
*/
val actualWidth = alignedTextureWidth(desiredWidth)
val actualHeight = desiredHeight
private val texture = makeDirectXTexture(device, 0, actualWidth, actualHeight).also {
if (it == 0L) {
throw RenderException("Can't allocate DirectX resources")
}
}
val backendRenderTarget = BackendRenderTarget(makeDirectXRenderTargetOffScreen(texture))
override fun close() {
backendRenderTarget.close()
disposeDirectXTexture(texture)
}
fun waitForCompletion() {
waitForCompletion(device, texture)
}
fun readPixels(byteArray: ByteArray) {
if (!readPixels(texture, byteArray)) {
throw RenderException("Couldn't read pixels")
}
}
}
}
package org.jetbrains.skiko.graphicapi
import org.jetbrains.skia.impl.NativePointer
import org.jetbrains.skiko.GpuPriority
import org.jetbrains.skiko.GraphicsApi
import org.jetbrains.skiko.Library
import org.jetbrains.skiko.hostOs
import org.jetbrains.skiko.isVideoCardSupported
internal object InternalDirectXApi {
init {
Library.load()
}
private external fun getTextureAlignment(): Long
private val rowBytesAlignment = getTextureAlignment().toInt()
private val widthSizeAlignment = rowBytesAlignment / 4
/**
* Calculate aligned width/height that is needed for performance optimization,
* since DirectX uses aligned bytebuffer.
*/
fun alignedTextureWidth(width: Int) = if (width % widthSizeAlignment != 0) {
width + widthSizeAlignment - (width % widthSizeAlignment);
} else {
width
}
// Called from native code
private fun isAdapterSupported(name: String) = isVideoCardSupported(GraphicsApi.DIRECT3D, hostOs, name)
fun chooseAdapter(adapterPriority: GpuPriority): NativePointer = chooseAdapter(adapterPriority.ordinal)
private external fun chooseAdapter(adapterPriority: Int): NativePointer
external fun createDirectXOffscreenDevice(adapter: NativePointer): NativePointer
external fun makeDirectXContext(device: NativePointer): NativePointer
external fun waitForCompletion(device: NativePointer, texturePtr: NativePointer)
external fun readPixels(texturePtr: NativePointer, byteArray: ByteArray): Boolean
/**
* Provides ID3D12Resource texture taking given [oldTexturePtr] into account
* since it can be reused if width and height are not changed,
* or the new one will be created.
*/
external fun makeDirectXTexture(device: NativePointer, oldTexturePtr: NativePointer, width: Int, height: Int): NativePointer
external fun disposeDirectXTexture(texturePtr: NativePointer)
external fun makeDirectXRenderTargetOffScreen(texturePtr: NativePointer): NativePointer
external fun disposeDevice(device: NativePointer)
}
...@@ -2,8 +2,19 @@ package org.jetbrains.skiko.swing ...@@ -2,8 +2,19 @@ package org.jetbrains.skiko.swing
import org.jetbrains.skia.* import org.jetbrains.skia.*
import org.jetbrains.skiko.* import org.jetbrains.skiko.*
import org.jetbrains.skiko.graphicapi.InternalDirectXApi.alignedTextureWidth
import org.jetbrains.skiko.graphicapi.InternalDirectXApi.createDirectXOffscreenDevice
import org.jetbrains.skiko.graphicapi.InternalDirectXApi.disposeDirectXTexture
import org.jetbrains.skiko.graphicapi.InternalDirectXApi.chooseAdapter
import org.jetbrains.skiko.graphicapi.InternalDirectXApi.disposeDevice
import org.jetbrains.skiko.graphicapi.InternalDirectXApi.makeDirectXContext
import org.jetbrains.skiko.graphicapi.InternalDirectXApi.makeDirectXRenderTargetOffScreen
import org.jetbrains.skiko.graphicapi.InternalDirectXApi.makeDirectXTexture
import org.jetbrains.skiko.graphicapi.InternalDirectXApi.readPixels
import org.jetbrains.skiko.graphicapi.InternalDirectXApi.waitForCompletion
import java.awt.Graphics2D import java.awt.Graphics2D
// TODO reuse DirectXOffscreenContext
internal class Direct3DSwingRedrawer( internal class Direct3DSwingRedrawer(
swingLayerProperties: SwingLayerProperties, swingLayerProperties: SwingLayerProperties,
private val renderDelegate: SkikoRenderDelegate, private val renderDelegate: SkikoRenderDelegate,
...@@ -15,7 +26,7 @@ internal class Direct3DSwingRedrawer( ...@@ -15,7 +26,7 @@ internal class Direct3DSwingRedrawer(
} }
} }
private val adapter = chooseAdapter(swingLayerProperties.adapterPriority.ordinal).also { private val adapter = chooseAdapter(swingLayerProperties.adapterPriority).also {
onDeviceChosen("DirectX12") // TODO: properly get name onDeviceChosen("DirectX12") // TODO: properly get name
} }
...@@ -33,8 +44,6 @@ internal class Direct3DSwingRedrawer( ...@@ -33,8 +44,6 @@ internal class Direct3DSwingRedrawer(
private var texturePtr: Long = 0 private var texturePtr: Long = 0
private var bytesToDraw = ByteArray(0) private var bytesToDraw = ByteArray(0)
private val rowBytesAlignment = getAlignment().toInt()
private val widthSizeAlignment = rowBytesAlignment / 4
init { init {
onContextInit() onContextInit()
...@@ -50,15 +59,9 @@ internal class Direct3DSwingRedrawer( ...@@ -50,15 +59,9 @@ internal class Direct3DSwingRedrawer(
override fun onRender(g: Graphics2D, width: Int, height: Int, nanoTime: Long) { override fun onRender(g: Graphics2D, width: Int, height: Int, nanoTime: Long) {
autoCloseScope { autoCloseScope {
// Calculate aligned width that is needed for performance optimization, // We will have [Surface] with width == [alignedWidth],
// since DirectX uses aligned bytebuffer.
// So we will have [Surface] with width == [alignedWidth],
// but imitate (for SkikoRenderDelegate and Swing) like it has width == [width]. // but imitate (for SkikoRenderDelegate and Swing) like it has width == [width].
val alignedWidth = if (width % widthSizeAlignment != 0) { val alignedWidth = alignedTextureWidth(width)
width + widthSizeAlignment - (width % widthSizeAlignment);
} else {
width
}
texturePtr = makeDirectXTexture(device, texturePtr, alignedWidth, height) texturePtr = makeDirectXTexture(device, texturePtr, alignedWidth, height)
if (texturePtr == 0L) { if (texturePtr == 0L) {
...@@ -90,7 +93,8 @@ internal class Direct3DSwingRedrawer( ...@@ -90,7 +93,8 @@ internal class Direct3DSwingRedrawer(
bytesToDraw = ByteArray(bytesArraySize) bytesToDraw = ByteArray(bytesArraySize)
} }
if(!readPixels(device, texturePtr, bytesToDraw)) { waitForCompletion(device, texturePtr)
if(!readPixels(texturePtr, bytesToDraw)) {
throw RenderException("Couldn't read pixels") throw RenderException("Couldn't read pixels")
} }
...@@ -100,27 +104,4 @@ internal class Direct3DSwingRedrawer( ...@@ -100,27 +104,4 @@ internal class Direct3DSwingRedrawer(
private fun makeRenderTarget() = BackendRenderTarget( private fun makeRenderTarget() = BackendRenderTarget(
makeDirectXRenderTargetOffScreen(texturePtr) makeDirectXRenderTargetOffScreen(texturePtr)
) )
// Called from native code
private fun isAdapterSupported(name: String) = isVideoCardSupported(GraphicsApi.DIRECT3D, hostOs, name)
private external fun chooseAdapter(adapterPriority: Int): Long
private external fun createDirectXOffscreenDevice(adapter: Long): Long
private external fun makeDirectXContext(device: Long): Long
private external fun readPixels(device: Long, texturePtr: Long, byteArray: ByteArray): Boolean
private external fun getAlignment(): Long
/**
* Provides ID3D12Resource texture taking given [oldTexturePtr] into account
* since it can be reused if width and height are not changed,
* or the new one will be created.
*/
private external fun makeDirectXTexture(device: Long, oldTexturePtr: Long, width: Int, height: Int): Long
private external fun disposeDirectXTexture(texturePtr: Long)
private external fun makeDirectXRenderTargetOffScreen(texturePtr: Long): Long
private external fun disposeDevice(device: Long)
} }
\ No newline at end of file
...@@ -28,6 +28,7 @@ open class SkiaSwingLayer( ...@@ -28,6 +28,7 @@ open class SkiaSwingLayer(
renderDelegate: SkikoRenderDelegate, renderDelegate: SkikoRenderDelegate,
analytics: SkiaLayerAnalytics = SkiaLayerAnalytics.Empty, analytics: SkiaLayerAnalytics = SkiaLayerAnalytics.Empty,
externalAccessibleFactory: ((Component) -> Accessible)? = null, externalAccessibleFactory: ((Component) -> Accessible)? = null,
private val properties: SkiaLayerProperties = SkiaLayerProperties()
) : JPanel() { ) : JPanel() {
internal companion object { internal companion object {
init { init {
...@@ -35,8 +36,6 @@ open class SkiaSwingLayer( ...@@ -35,8 +36,6 @@ open class SkiaSwingLayer(
} }
} }
private val properties = SkiaLayerProperties()
private var isInitialized = false private var isInitialized = false
@Volatile @Volatile
......
@file:OptIn(ExperimentalSkikoApi::class)
package org.jetbrains.skiko package org.jetbrains.skiko
import kotlinx.coroutines.* import kotlinx.coroutines.*
...@@ -11,6 +13,7 @@ import org.jetbrains.skia.paragraph.TextStyle ...@@ -11,6 +13,7 @@ import org.jetbrains.skia.paragraph.TextStyle
import org.jetbrains.skiko.context.JvmContextHandler import org.jetbrains.skiko.context.JvmContextHandler
import org.jetbrains.skiko.redrawer.MetalRedrawer import org.jetbrains.skiko.redrawer.MetalRedrawer
import org.jetbrains.skiko.redrawer.Redrawer import org.jetbrains.skiko.redrawer.Redrawer
import org.jetbrains.skiko.swing.SkiaSwingLayer
import org.jetbrains.skiko.util.ScreenshotTestRule import org.jetbrains.skiko.util.ScreenshotTestRule
import org.jetbrains.skiko.util.UiTestScope import org.jetbrains.skiko.util.UiTestScope
import org.jetbrains.skiko.util.UiTestWindow import org.jetbrains.skiko.util.UiTestWindow
...@@ -176,6 +179,35 @@ class SkiaLayerTest { ...@@ -176,6 +179,35 @@ class SkiaLayerTest {
} }
} }
@Test
fun `render single swing layer`() = uiTest {
val window = JFrame()
val app = RectRenderer(window, 200, 100, Color.RED)
val layer = SkiaSwingLayer(
app,
properties = SkiaLayerProperties(renderApi = renderApi)
)
window.contentPane.add(layer)
try {
window.setLocation(200, 200)
window.setSize(400, 200)
layer.setSize(400, 200)
window.defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
window.isUndecorated = true
window.isVisible = true
delay(1000)
screenshots.assert(window.bounds, "frame1")
app.rectWidth = 100
layer.repaint()
delay(1000)
screenshots.assert(window.bounds, "frame2")
} finally {
window.close()
}
}
@Test @Test
fun `render single window before window show`() = uiTest { fun `render single window before window show`() = uiTest {
val window = UiTestWindow() val window = UiTestWindow()
...@@ -968,17 +1000,36 @@ class SkiaLayerTest { ...@@ -968,17 +1000,36 @@ class SkiaLayerTest {
} }
private class RectRenderer( private class RectRenderer(
private val layer: SkiaLayer, private val getContentScale: () -> Float,
var rectWidth: Int, var rectWidth: Int,
var rectHeight: Int, var rectHeight: Int,
private val rectColor: Color private val rectColor: Color
) : SkikoRenderDelegate { ) : SkikoRenderDelegate {
constructor(
layer: SkiaLayer,
rectWidth: Int,
rectHeight: Int,
rectColor: Color
) : this(
{ layer.contentScale }, rectWidth, rectHeight, rectColor
)
constructor(
layer: JFrame,
rectWidth: Int,
rectHeight: Int,
rectColor: Color
) : this(
{ layer.graphicsConfiguration.defaultTransform.scaleX.toFloat() }, rectWidth, rectHeight, rectColor
)
private val contentScale get() = getContentScale()
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) { override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
val dpi = layer.contentScale
canvas.drawRect(Rect(0f, 0f, width.toFloat(), height.toFloat()), Paint().apply { canvas.drawRect(Rect(0f, 0f, width.toFloat(), height.toFloat()), Paint().apply {
color = Color.WHITE.rgb color = Color.WHITE.rgb
}) })
canvas.drawRect(Rect(0f, 0f, rectWidth * dpi, rectHeight * dpi), Paint().apply { canvas.drawRect(Rect(0f, 0f, rectWidth * contentScale, rectHeight * contentScale), Paint().apply {
color = rectColor.rgb color = rectColor.rgb
}) })
} }
......
package org.jetbrains.skiko.graphicapi
import org.jetbrains.skia.Color
import org.jetbrains.skia.ColorSpace
import org.jetbrains.skia.Paint
import org.jetbrains.skia.PixelGeometry
import org.jetbrains.skia.Rect
import org.jetbrains.skia.Surface
import org.jetbrains.skia.SurfaceColorFormat
import org.jetbrains.skia.SurfaceOrigin
import org.jetbrains.skia.SurfaceProps
import org.jetbrains.skiko.ExperimentalSkikoApi
import org.jetbrains.skiko.RenderException
import org.jetbrains.skiko.autoCloseScope
import org.jetbrains.skiko.hostOs
import org.junit.Assume.assumeTrue
import org.junit.Test
import kotlin.test.assertEquals
@OptIn(ExperimentalSkikoApi::class)
class DirectXOffscreenContextTest {
@Test
fun readPixels() = autoCloseScope {
assumeTrue(hostOs.isWindows)
val context = try {
DirectXOffscreenContext().autoClose()
} catch (e: RenderException) {
println("Cannot create DirectX12 context, skipping test. Stacktrace:")
e.printStackTrace()
return@autoCloseScope
}
val texture = context.Texture(desiredWidth = 200, desiredHeight = 100).autoClose()
var textureBytes = ByteArray(texture.actualWidth * texture.actualHeight * 4)
val surface = Surface.makeFromBackendRenderTarget(
context.directContext,
texture.backendRenderTarget,
SurfaceOrigin.TOP_LEFT,
SurfaceColorFormat.BGRA_8888,
ColorSpace.sRGB,
SurfaceProps(pixelGeometry = PixelGeometry.UNKNOWN)
)?.autoClose() ?: throw RenderException("Cannot create surface")
fun drawFrame(color: Int) {
val canvas = surface.canvas
canvas.drawRect(Rect(0f, 0f, texture.actualWidth.toFloat(), texture.actualHeight.toFloat()), Paint().apply {
this.color = color
})
surface.flushAndSubmit(syncCpu = false)
texture.waitForCompletion()
texture.readPixels(textureBytes)
}
drawFrame(Color.RED)
assertEquals(
listOf(0, 0, -1, -1), // BGRA format, -1 color is the same as 255 color
textureBytes.take(4).map { it.toInt() }
)
drawFrame(Color.BLUE)
assertEquals(
listOf(-1, 0, 0, -1),
textureBytes.take(4).map { it.toInt() }
)
}
}
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