Skip to content

Commit 1af72c1

Browse files
authored
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
1 parent 84bff4d commit 1af72c1

File tree

9 files changed

+285
-52
lines changed

9 files changed

+285
-52
lines changed

skiko/src/awtMain/cpp/windows/directXSwingRedrawer.cc renamed to skiko/src/awtMain/cpp/windows/InternalDirectXApi.cc

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -150,14 +150,14 @@ extern "C"
150150
std::string name(tmp.begin(), tmp.end());
151151
jstring jname = env->NewStringUTF(name.c_str());
152152

153-
static jclass cls = (jclass) env->NewGlobalRef(env->FindClass("org/jetbrains/skiko/swing/Direct3DSwingRedrawer"));
153+
static jclass cls = (jclass) env->NewGlobalRef(env->FindClass("org/jetbrains/skiko/graphicapi/InternalDirectXApi"));
154154
static jmethodID method = env->GetMethodID(cls, "isAdapterSupported", "(Ljava/lang/String;)Z");
155155

156156
return env->CallBooleanMethod(redrawer, method, jname);
157157
}
158158

159159
// TODO: extract common code with directXRedrawer
160-
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_swing_Direct3DSwingRedrawer_chooseAdapter(
160+
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_graphicapi_InternalDirectXApi_chooseAdapter(
161161
JNIEnv *env, jobject redrawer, jint adapterPriority) {
162162
gr_cp<IDXGIFactory4> deviceFactory;
163163
if (!SUCCEEDED(CreateDXGIFactory1(IID_PPV_ARGS(&deviceFactory)))) {
@@ -187,7 +187,7 @@ extern "C"
187187
return 0;
188188
}
189189

190-
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_swing_Direct3DSwingRedrawer_createDirectXOffscreenDevice(
190+
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_graphicapi_InternalDirectXApi_createDirectXOffscreenDevice(
191191
JNIEnv *env, jobject redrawer, jlong adapterPtr) {
192192

193193
gr_cp<IDXGIFactory4> deviceFactory;
@@ -260,7 +260,7 @@ extern "C"
260260
return toJavaPointer(d3dDevice);
261261
}
262262

263-
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_swing_Direct3DSwingRedrawer_makeDirectXRenderTargetOffScreen(
263+
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_graphicapi_InternalDirectXApi_makeDirectXRenderTargetOffScreen(
264264
JNIEnv *env, jobject redrawer, jlong texturePtr) {
265265
DirectXOffScreenTexture *texture = fromJavaPointer<DirectXOffScreenTexture *>(texturePtr);
266266
ID3D12Resource* resource = texture->resource;
@@ -275,15 +275,15 @@ extern "C"
275275
return reinterpret_cast<jlong>(renderTarget);
276276
}
277277

278-
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_swing_Direct3DSwingRedrawer_makeDirectXContext(
278+
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_graphicapi_InternalDirectXApi_makeDirectXContext(
279279
JNIEnv *env, jobject redrawer, jlong devicePtr)
280280
{
281281
DirectXOffscreenDevice *d3dDevice = fromJavaPointer<DirectXOffscreenDevice *>(devicePtr);
282282
GrD3DBackendContext backendContext = d3dDevice->backendContext;
283283
return toJavaPointer(GrDirectContext::MakeDirect3D(backendContext).release());
284284
}
285285

286-
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_swing_Direct3DSwingRedrawer_makeDirectXTexture(
286+
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_graphicapi_InternalDirectXApi_makeDirectXTexture(
287287
JNIEnv *env, jobject redrawer, jlong devicePtr, jlong oldTexturePtr, jint width, jint height) {
288288
DirectXOffscreenDevice *device = fromJavaPointer<DirectXOffscreenDevice *>(devicePtr);
289289
DirectXOffScreenTexture *oldTexture = fromJavaPointer<DirectXOffScreenTexture *>(oldTexturePtr);
@@ -307,15 +307,14 @@ extern "C"
307307
return toJavaPointer(texture);
308308
}
309309

310-
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_swing_Direct3DSwingRedrawer_disposeDirectXTexture(
310+
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_graphicapi_InternalDirectXApi_disposeDirectXTexture(
311311
JNIEnv *env, jobject redrawer, jlong texturePtr) {
312312
DirectXOffScreenTexture *texture = fromJavaPointer<DirectXOffScreenTexture *>(texturePtr);
313313
delete texture;
314314
}
315315

316-
JNIEXPORT jboolean JNICALL Java_org_jetbrains_skiko_swing_Direct3DSwingRedrawer_readPixels(
317-
JNIEnv *env, jobject redrawer, jlong devicePtr, jlong texturePtr, jbyteArray byteArray) {
318-
jbyte *bytesPtr = env->GetByteArrayElements(byteArray, nullptr);
316+
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_graphicapi_InternalDirectXApi_waitForCompletion(
317+
JNIEnv *env, jobject redrawer, jlong devicePtr, jlong texturePtr) {
319318

320319
DirectXOffscreenDevice *device = fromJavaPointer<DirectXOffscreenDevice *>(devicePtr);
321320

@@ -373,6 +372,13 @@ extern "C"
373372
fence->SetEventOnCompletion(fenceValue, fenceEvent);
374373
WaitForSingleObject(fenceEvent, INFINITE);
375374
}
375+
}
376+
377+
JNIEXPORT jboolean JNICALL Java_org_jetbrains_skiko_graphicapi_InternalDirectXApi_readPixels(
378+
JNIEnv *env, jobject redrawer, jlong texturePtr, jbyteArray byteArray) {
379+
jbyte *bytesPtr = env->GetByteArrayElements(byteArray, nullptr);
380+
381+
DirectXOffScreenTexture *texture = fromJavaPointer<DirectXOffScreenTexture *>(texturePtr);
376382

377383
auto rangeLength = texture->readbackBufferWidth();
378384
D3D12_RANGE readbackBufferRange{ 0, rangeLength };
@@ -408,13 +414,13 @@ extern "C"
408414
return true;
409415
}
410416

411-
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_swing_Direct3DSwingRedrawer_disposeDevice(
417+
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_graphicapi_InternalDirectXApi_disposeDevice(
412418
JNIEnv *env, jobject redrawer, jlong devicePtr) {
413419
DirectXOffscreenDevice *device = fromJavaPointer<DirectXOffscreenDevice *>(devicePtr);
414420
delete device;
415421
}
416422

417-
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_swing_Direct3DSwingRedrawer_getAlignment(
423+
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_graphicapi_InternalDirectXApi_getTextureAlignment(
418424
JNIEnv *env, jobject redrawer) {
419425
return D3D12_TEXTURE_DATA_PITCH_ALIGNMENT;
420426
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package org.jetbrains.skiko.graphicapi
2+
3+
import org.jetbrains.skia.BackendRenderTarget
4+
import org.jetbrains.skia.DirectContext
5+
import org.jetbrains.skiko.ExperimentalSkikoApi
6+
import org.jetbrains.skiko.GpuPriority
7+
import org.jetbrains.skiko.RenderException
8+
import org.jetbrains.skiko.graphicapi.InternalDirectXApi.alignedTextureWidth
9+
import org.jetbrains.skiko.graphicapi.InternalDirectXApi.chooseAdapter
10+
import org.jetbrains.skiko.graphicapi.InternalDirectXApi.createDirectXOffscreenDevice
11+
import org.jetbrains.skiko.graphicapi.InternalDirectXApi.disposeDevice
12+
import org.jetbrains.skiko.graphicapi.InternalDirectXApi.disposeDirectXTexture
13+
import org.jetbrains.skiko.graphicapi.InternalDirectXApi.makeDirectXContext
14+
import org.jetbrains.skiko.graphicapi.InternalDirectXApi.makeDirectXRenderTargetOffScreen
15+
import org.jetbrains.skiko.graphicapi.InternalDirectXApi.makeDirectXTexture
16+
import org.jetbrains.skiko.graphicapi.InternalDirectXApi.readPixels
17+
import org.jetbrains.skiko.graphicapi.InternalDirectXApi.waitForCompletion
18+
19+
/**
20+
* Class that allows drawing into offscreen DirectX texture.
21+
*
22+
* Used in the `benchmarks` project:
23+
* https://github.com/JetBrains/compose-multiplatform/blob/cedd48f99e877f8b936ff574812d573baf231307/benchmarks/multiplatform/benchmarks/src/commonMain/kotlin/MeasureComposable.kt#L16
24+
*/
25+
@ExperimentalSkikoApi
26+
class DirectXOffscreenContext : AutoCloseable {
27+
private val adapter = chooseAdapter(GpuPriority.Integrated)
28+
29+
private val device = createDirectXOffscreenDevice(adapter).also {
30+
if (it == 0L) {
31+
throw RenderException("Failed to create DirectX12 device.")
32+
}
33+
}
34+
35+
val directContext = DirectContext(makeDirectXContext(device))
36+
37+
override fun close() {
38+
directContext.close()
39+
disposeDevice(device)
40+
}
41+
42+
inner class Texture(desiredWidth: Int, desiredHeight: Int) : AutoCloseable {
43+
/**
44+
* Aligned width/height that is needed for performance optimization,
45+
* since DirectX uses aligned bytebuffer.
46+
*/
47+
val actualWidth = alignedTextureWidth(desiredWidth)
48+
49+
val actualHeight = desiredHeight
50+
51+
private val texture = makeDirectXTexture(device, 0, actualWidth, actualHeight).also {
52+
if (it == 0L) {
53+
throw RenderException("Can't allocate DirectX resources")
54+
}
55+
}
56+
57+
val backendRenderTarget = BackendRenderTarget(makeDirectXRenderTargetOffScreen(texture))
58+
59+
override fun close() {
60+
backendRenderTarget.close()
61+
disposeDirectXTexture(texture)
62+
}
63+
64+
fun waitForCompletion() {
65+
waitForCompletion(device, texture)
66+
}
67+
68+
fun readPixels(byteArray: ByteArray) {
69+
if (!readPixels(texture, byteArray)) {
70+
throw RenderException("Couldn't read pixels")
71+
}
72+
}
73+
}
74+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package org.jetbrains.skiko.graphicapi
2+
3+
import org.jetbrains.skia.impl.NativePointer
4+
import org.jetbrains.skiko.GpuPriority
5+
import org.jetbrains.skiko.GraphicsApi
6+
import org.jetbrains.skiko.Library
7+
import org.jetbrains.skiko.hostOs
8+
import org.jetbrains.skiko.isVideoCardSupported
9+
10+
internal object InternalDirectXApi {
11+
init {
12+
Library.load()
13+
}
14+
15+
private external fun getTextureAlignment(): Long
16+
private val rowBytesAlignment = getTextureAlignment().toInt()
17+
private val widthSizeAlignment = rowBytesAlignment / 4
18+
19+
/**
20+
* Calculate aligned width/height that is needed for performance optimization,
21+
* since DirectX uses aligned bytebuffer.
22+
*/
23+
fun alignedTextureWidth(width: Int) = if (width % widthSizeAlignment != 0) {
24+
width + widthSizeAlignment - (width % widthSizeAlignment);
25+
} else {
26+
width
27+
}
28+
29+
// Called from native code
30+
private fun isAdapterSupported(name: String) = isVideoCardSupported(GraphicsApi.DIRECT3D, hostOs, name)
31+
32+
fun chooseAdapter(adapterPriority: GpuPriority): NativePointer = chooseAdapter(adapterPriority.ordinal)
33+
private external fun chooseAdapter(adapterPriority: Int): NativePointer
34+
external fun createDirectXOffscreenDevice(adapter: NativePointer): NativePointer
35+
external fun makeDirectXContext(device: NativePointer): NativePointer
36+
37+
external fun waitForCompletion(device: NativePointer, texturePtr: NativePointer)
38+
external fun readPixels(texturePtr: NativePointer, byteArray: ByteArray): Boolean
39+
40+
41+
/**
42+
* Provides ID3D12Resource texture taking given [oldTexturePtr] into account
43+
* since it can be reused if width and height are not changed,
44+
* or the new one will be created.
45+
*/
46+
external fun makeDirectXTexture(device: NativePointer, oldTexturePtr: NativePointer, width: Int, height: Int): NativePointer
47+
external fun disposeDirectXTexture(texturePtr: NativePointer)
48+
49+
external fun makeDirectXRenderTargetOffScreen(texturePtr: NativePointer): NativePointer
50+
51+
external fun disposeDevice(device: NativePointer)
52+
}

skiko/src/awtMain/kotlin/org/jetbrains/skiko/swing/Direct3DSwingRedrawer.kt

Lines changed: 16 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,19 @@ package org.jetbrains.skiko.swing
22

33
import org.jetbrains.skia.*
44
import org.jetbrains.skiko.*
5+
import org.jetbrains.skiko.graphicapi.InternalDirectXApi.alignedTextureWidth
6+
import org.jetbrains.skiko.graphicapi.InternalDirectXApi.createDirectXOffscreenDevice
7+
import org.jetbrains.skiko.graphicapi.InternalDirectXApi.disposeDirectXTexture
8+
import org.jetbrains.skiko.graphicapi.InternalDirectXApi.chooseAdapter
9+
import org.jetbrains.skiko.graphicapi.InternalDirectXApi.disposeDevice
10+
import org.jetbrains.skiko.graphicapi.InternalDirectXApi.makeDirectXContext
11+
import org.jetbrains.skiko.graphicapi.InternalDirectXApi.makeDirectXRenderTargetOffScreen
12+
import org.jetbrains.skiko.graphicapi.InternalDirectXApi.makeDirectXTexture
13+
import org.jetbrains.skiko.graphicapi.InternalDirectXApi.readPixels
14+
import org.jetbrains.skiko.graphicapi.InternalDirectXApi.waitForCompletion
515
import java.awt.Graphics2D
616

17+
// TODO reuse DirectXOffscreenContext
718
internal class Direct3DSwingRedrawer(
819
swingLayerProperties: SwingLayerProperties,
920
private val renderDelegate: SkikoRenderDelegate,
@@ -15,7 +26,7 @@ internal class Direct3DSwingRedrawer(
1526
}
1627
}
1728

18-
private val adapter = chooseAdapter(swingLayerProperties.adapterPriority.ordinal).also {
29+
private val adapter = chooseAdapter(swingLayerProperties.adapterPriority).also {
1930
onDeviceChosen("DirectX12") // TODO: properly get name
2031
}
2132

@@ -33,8 +44,6 @@ internal class Direct3DSwingRedrawer(
3344

3445
private var texturePtr: Long = 0
3546
private var bytesToDraw = ByteArray(0)
36-
private val rowBytesAlignment = getAlignment().toInt()
37-
private val widthSizeAlignment = rowBytesAlignment / 4
3847

3948
init {
4049
onContextInit()
@@ -50,15 +59,9 @@ internal class Direct3DSwingRedrawer(
5059

5160
override fun onRender(g: Graphics2D, width: Int, height: Int, nanoTime: Long) {
5261
autoCloseScope {
53-
// Calculate aligned width that is needed for performance optimization,
54-
// since DirectX uses aligned bytebuffer.
55-
// So we will have [Surface] with width == [alignedWidth],
62+
// We will have [Surface] with width == [alignedWidth],
5663
// but imitate (for SkikoRenderDelegate and Swing) like it has width == [width].
57-
val alignedWidth = if (width % widthSizeAlignment != 0) {
58-
width + widthSizeAlignment - (width % widthSizeAlignment);
59-
} else {
60-
width
61-
}
64+
val alignedWidth = alignedTextureWidth(width)
6265

6366
texturePtr = makeDirectXTexture(device, texturePtr, alignedWidth, height)
6467
if (texturePtr == 0L) {
@@ -90,7 +93,8 @@ internal class Direct3DSwingRedrawer(
9093
bytesToDraw = ByteArray(bytesArraySize)
9194
}
9295

93-
if(!readPixels(device, texturePtr, bytesToDraw)) {
96+
waitForCompletion(device, texturePtr)
97+
if(!readPixels(texturePtr, bytesToDraw)) {
9498
throw RenderException("Couldn't read pixels")
9599
}
96100

@@ -100,27 +104,4 @@ internal class Direct3DSwingRedrawer(
100104
private fun makeRenderTarget() = BackendRenderTarget(
101105
makeDirectXRenderTargetOffScreen(texturePtr)
102106
)
103-
104-
// Called from native code
105-
private fun isAdapterSupported(name: String) = isVideoCardSupported(GraphicsApi.DIRECT3D, hostOs, name)
106-
107-
private external fun chooseAdapter(adapterPriority: Int): Long
108-
private external fun createDirectXOffscreenDevice(adapter: Long): Long
109-
private external fun makeDirectXContext(device: Long): Long
110-
111-
private external fun readPixels(device: Long, texturePtr: Long, byteArray: ByteArray): Boolean
112-
113-
private external fun getAlignment(): Long
114-
115-
/**
116-
* Provides ID3D12Resource texture taking given [oldTexturePtr] into account
117-
* since it can be reused if width and height are not changed,
118-
* or the new one will be created.
119-
*/
120-
private external fun makeDirectXTexture(device: Long, oldTexturePtr: Long, width: Int, height: Int): Long
121-
private external fun disposeDirectXTexture(texturePtr: Long)
122-
123-
private external fun makeDirectXRenderTargetOffScreen(texturePtr: Long): Long
124-
125-
private external fun disposeDevice(device: Long)
126107
}

skiko/src/awtMain/kotlin/org/jetbrains/skiko/swing/SkiaSwingLayer.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,14 @@ open class SkiaSwingLayer(
2828
renderDelegate: SkikoRenderDelegate,
2929
analytics: SkiaLayerAnalytics = SkiaLayerAnalytics.Empty,
3030
externalAccessibleFactory: ((Component) -> Accessible)? = null,
31+
private val properties: SkiaLayerProperties = SkiaLayerProperties()
3132
) : JPanel() {
3233
internal companion object {
3334
init {
3435
Library.load()
3536
}
3637
}
3738

38-
private val properties = SkiaLayerProperties()
39-
4039
private var isInitialized = false
4140

4241
@Volatile

0 commit comments

Comments
 (0)