Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
S
skiko
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
liuqiang
skiko
Commits
fae037a0
Unverified
Commit
fae037a0
authored
Feb 03, 2021
by
Igor Demin
Committed by
GitHub
Feb 03, 2021
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #60 from JetBrains/macos-update
macOs. call update only once per draw if vsync is disabled
parents
50223f6b
c5bc301e
Changes
5
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
277 additions
and
17 deletions
+277
-17
build.gradle.kts
skiko/build.gradle.kts
+12
-1
Task.kt
skiko/src/jvmMain/kotlin/org/jetbrains/skiko/Task.kt
+28
-0
MacOsOpenGLRedrawer.kt
...otlin/org/jetbrains/skiko/redrawer/MacOsOpenGLRedrawer.kt
+36
-15
redrawer.m
skiko/src/jvmMain/objectiveC/macos/redrawer.m
+1
-1
TaskTest.kt
skiko/src/jvmTest/kotlin/org/jetbrains/skiko/TaskTest.kt
+200
-0
No files found.
skiko/build.gradle.kts
View file @
fae037a0
import
de.undercouch.gradle.tasks.download.Download
import
org.gradle.crypto.checksum.Checksum
import
org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import
org.jetbrains.kotlin.gradle.tasks.KotlinTest
plugins
{
kotlin
(
"multiplatform"
)
version
"1.3.72"
...
...
@@ -9,6 +11,8 @@ plugins {
id
(
"de.undercouch.download"
)
version
"4.1.1"
}
val
coroutinesVersion
=
"1.4.1"
buildscript
{
dependencies
{
classpath
(
"org.kohsuke:github-api:1.116"
)
...
...
@@ -166,7 +170,7 @@ kotlin {
kotlin
.
srcDirs
(
skijaSrcDir
)
dependencies
{
implementation
(
kotlin
(
"stdlib-jdk8"
))
implementation
(
"org.jetbrains.kotlinx:kotlinx-coroutines-swing:
1.4.1
"
)
implementation
(
"org.jetbrains.kotlinx:kotlinx-coroutines-swing:
$coroutinesVersion
"
)
compileOnly
(
lombok
)
compileOnly
(
jetbrainsAnnotations
)
}
...
...
@@ -174,6 +178,7 @@ kotlin {
}
val
jvmTest
by
getting
{
dependencies
{
implementation
(
"org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion"
)
implementation
(
kotlin
(
"test-junit"
))
}
}
...
...
@@ -565,3 +570,9 @@ publishing {
}
}
}
tasks
.
withType
<
KotlinCompile
>().
configureEach
{
if
(
name
==
"compileTestKotlinJvm"
)
{
kotlinOptions
.
freeCompilerArgs
+=
"-Xopt-in=kotlin.RequiresOptIn"
}
}
\ No newline at end of file
skiko/src/jvmMain/kotlin/org/jetbrains/skiko/Task.kt
0 → 100644
View file @
fae037a0
package
org.jetbrains.skiko
import
kotlinx.coroutines.channels.Channel
import
java.util.concurrent.atomic.AtomicBoolean
internal
class
Task
{
private
val
onFinish
=
Channel
<
Unit
>(
1
)
private
var
done
=
AtomicBoolean
(
true
)
/**
* Run task and await its finishing (i.e. calling of [finish])
*/
suspend
fun
runAndAwait
(
run
:
suspend
()
->
Unit
)
{
done
.
set
(
false
)
run
()
onFinish
.
receive
()
}
/**
* Finish running task. If there is no running task, do nothing.
*/
fun
finish
()
{
if
(!
done
.
getAndSet
(
true
))
{
onFinish
.
offer
(
Unit
)
}
}
}
\ No newline at end of file
skiko/src/jvmMain/kotlin/org/jetbrains/skiko/redrawer/MacOsOpenGLRedrawer.kt
View file @
fae037a0
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
org.jetbrains.skiko.SkikoProperties
import
org.jetbrains.skiko.Task
import
javax.swing.SwingUtilities.convertPoint
import
javax.swing.SwingUtilities.getRootPane
...
...
@@ -30,7 +30,7 @@ internal class MacOsOpenGLRedrawer(
// AWT has a method to avoid dead locks but it is internal (sun.lwawt.macosx.LWCToolkit.invokeAndWait)
private
val
vsyncLayer
=
object
:
AWTGLLayer
(
containerLayerPtr
,
setNeedsDisplayOnBoundsChange
=
false
)
{
@Volatile
private
var
needDraw
:
CompletableDeferred
<
Unit
>?
=
null
private
var
canDraw
=
false
init
{
setFrame
(
0
,
0
,
1
,
1
)
// if frame has zero size then it will be not drawn at all
...
...
@@ -41,21 +41,17 @@ internal class MacOsOpenGLRedrawer(
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
val
canDraw
=
canDraw
if
(!
canDraw
)
{
isAsynchronous
=
false
// stop asynchronous mode so we don't waste CPU cycles
}
return
canDraw
}
suspend
fun
sync
()
{
check
(
needDraw
==
null
)
needDraw
=
CompletableDeferred
()
override
fun
setNeedsDisplay
()
{
// Use asynchronous mode instead of just setNeedsDisplay,
// so Core Animation will wait for the next frame in vsync signal
//
...
...
@@ -68,17 +64,26 @@ internal class MacOsOpenGLRedrawer(
isAsynchronous
=
true
super
.
setNeedsDisplay
()
}
}
needDraw
!!
.
await
()
needDraw
=
null
suspend
fun
sync
()
{
canDraw
=
true
display
()
canDraw
=
false
}
}
private
val
frameDispatcher
=
FrameDispatcher
(
Dispatchers
.
Swing
)
{
synchronized
(
drawLock
)
{
layer
.
update
(
System
.
nanoTime
())
drawLayer
.
setNeedsDisplay
()
}
if
(
SkikoProperties
.
vsyncEnabled
)
{
drawLayer
.
setNeedsDisplay
()
vsyncLayer
.
sync
()
}
else
{
// If vsync is disabled we should await the drawing to end.
// Otherwise we will call 'update' multiple times.
drawLayer
.
display
()
}
}
...
...
@@ -111,10 +116,12 @@ internal class MacOsOpenGLRedrawer(
}
}
private
open
class
AWTGLLayer
(
private
val
containerPtr
:
Long
,
setNeedsDisplayOnBoundsChange
:
Boolean
)
{
private
abstract
class
AWTGLLayer
(
private
val
containerPtr
:
Long
,
setNeedsDisplayOnBoundsChange
:
Boolean
)
{
@Suppress
(
"LeakingThis"
)
val
ptr
=
initAWTGLLayer
(
containerPtr
,
this
,
setNeedsDisplayOnBoundsChange
)
private
val
display
=
Task
()
fun
setFrame
(
x
:
Int
,
y
:
Int
,
width
:
Int
,
height
:
Int
)
{
setFrame
(
containerPtr
,
ptr
,
x
.
toFloat
(),
y
.
toFloat
(),
width
.
toFloat
(),
height
.
toFloat
())
}
...
...
@@ -128,11 +135,25 @@ private open class AWTGLLayer(private val containerPtr: Long, setNeedsDisplayOnB
open
fun
setNeedsDisplay
()
=
setNeedsDisplayOnMainThread
(
ptr
)
suspend
fun
display
()
=
display
.
runAndAwait
{
setNeedsDisplay
()
}
// Called in AppKit Thread
protected
open
fun
canDraw
()
=
true
@Suppress
(
"unused"
)
// called from native code
private
fun
performDraw
()
{
try
{
draw
()
}
catch
(
e
:
Throwable
)
{
e
.
printStackTrace
()
}
display
.
finish
()
}
// Called in AppKit Thread
protected
open
fun
draw
()
=
Unit
protected
abstract
fun
draw
()
private
external
fun
isAsynchronous
(
ptr
:
Long
):
Boolean
private
external
fun
setAsynchronous
(
ptr
:
Long
,
isAsynchronous
:
Boolean
)
...
...
skiko/src/jvmMain/objectiveC/macos/redrawer.m
View file @
fae037a0
...
...
@@ -61,7 +61,7 @@ JavaVM *jvm = NULL;
static
jclass
cls
=
NULL
;
static
jmethodID
method
=
NULL
;
if
(
!
cls
)
cls
=
(
*
env
)
->
GetObjectClass
(
env
,
self
.
javaRef
);
if
(
!
method
)
method
=
(
*
env
)
->
GetMethodID
(
env
,
cls
,
"
d
raw"
,
"()V"
);
if
(
!
method
)
method
=
(
*
env
)
->
GetMethodID
(
env
,
cls
,
"
performD
raw"
,
"()V"
);
(
*
env
)
->
CallVoidMethod
(
env
,
self
.
javaRef
,
method
);
[
super
drawInCGLContext
:
ctx
pixelFormat
:
pf
forLayerTime
:
t
displayTime
:
ts
];
...
...
skiko/src/jvmTest/kotlin/org/jetbrains/skiko/TaskTest.kt
0 → 100644
View file @
fae037a0
package
org.jetbrains.skiko
import
kotlinx.coroutines.CoroutineScope
import
kotlinx.coroutines.Dispatchers
import
kotlinx.coroutines.ExperimentalCoroutinesApi
import
kotlinx.coroutines.Job
import
kotlinx.coroutines.asCoroutineDispatcher
import
kotlinx.coroutines.isActive
import
kotlinx.coroutines.launch
import
kotlinx.coroutines.runBlocking
import
kotlinx.coroutines.test.TestCoroutineScope
import
kotlinx.coroutines.test.runBlockingTest
import
kotlinx.coroutines.yield
import
org.junit.Assert.assertFalse
import
org.junit.Assert.assertTrue
import
org.junit.Test
import
java.util.concurrent.Executors.newSingleThreadExecutor
import
java.util.concurrent.atomic.AtomicBoolean
import
kotlin.random.Random
@OptIn
(
ExperimentalCoroutinesApi
::
class
)
internal
class
TaskTest
{
@Test
fun
`
runAndAwait
with
finish
`
()
=
test
{
val
task
=
Task
()
val
job
=
launch
{
task
.
runAndAwait
{}
}
advanceUntilIdle
()
task
.
finish
()
advanceUntilIdle
()
assertTrue
(
job
.
isCompleted
)
}
@Test
fun
`
runAndAwait
without
finish
`
()
=
test
{
val
task
=
Task
()
val
job
=
launch
{
task
.
runAndAwait
{}
}
advanceUntilIdle
()
assertFalse
(
job
.
isCompleted
)
}
@Test
fun
`
finish
inside
runAndAwait
`
()
=
test
{
val
task
=
Task
()
val
job
=
launch
{
task
.
runAndAwait
{
task
.
finish
()
}
}
advanceUntilIdle
()
assertTrue
(
job
.
isCompleted
)
}
@Test
fun
`
finish
before
runAndAwait
`
()
=
test
{
val
task
=
Task
()
val
job
=
launch
{
task
.
finish
()
task
.
runAndAwait
{}
}
advanceUntilIdle
()
assertFalse
(
job
.
isCompleted
)
}
@Test
(
timeout
=
5000
)
fun
`
finish
in
another
thread
`
()
{
val
task
=
Task
()
runBlocking
{
repeat
(
1000
)
{
task
.
runAndAwait
{
launch
(
Dispatchers
.
IO
)
{
task
.
finish
()
}
}
}
}
}
@Test
(
timeout
=
5000
)
fun
`
simulate
MacOs
layer
`
()
{
runBlocking
{
val
job
=
Job
()
val
layer
=
object
:
MacOsSimulatedLayer
(
scope
=
CoroutineScope
(
coroutineContext
+
job
))
{
val
draw
=
Task
()
override
fun
draw
()
{
draw
.
finish
()
}
suspend
fun
display
()
{
draw
.
runAndAwait
{
setNeedsDisplay
()
}
}
}
repeat
(
10000
)
{
layer
.
display
()
}
job
.
cancel
()
}
}
@Test
(
timeout
=
5000
)
fun
`
simulate
MacOs
layer
with
another
renderings
`
()
{
runBlocking
{
val
job
=
Job
()
val
layer
=
object
:
MacOsSimulatedLayer
(
scope
=
CoroutineScope
(
coroutineContext
+
job
))
{
val
draw
=
Task
()
override
fun
draw
()
{
draw
.
finish
()
}
suspend
fun
display
()
{
draw
.
runAndAwait
{
setNeedsDisplay
()
}
}
}
val
random
=
Random
(
42
)
val
anotherRenderingsJob1
=
launch
(
newSingleThreadExecutor
().
asCoroutineDispatcher
())
{
while
(
true
)
{
repeat
(
random
.
nextInt
(
4
))
{
layer
.
setNeedsDisplay
()
}
yield
()
}
}
val
anotherRenderingsJob2
=
launch
(
newSingleThreadExecutor
().
asCoroutineDispatcher
())
{
while
(
true
)
{
repeat
(
random
.
nextInt
(
4
))
{
layer
.
setNeedsDisplay
()
}
yield
()
}
}
repeat
(
10000
)
{
layer
.
display
()
}
job
.
cancel
()
anotherRenderingsJob1
.
cancel
()
anotherRenderingsJob2
.
cancel
()
}
}
private
abstract
class
MacOsSimulatedLayer
(
scope
:
CoroutineScope
)
{
private
val
dispatcher
=
newSingleThreadExecutor
().
asCoroutineDispatcher
()
private
var
needsDisplay
=
AtomicBoolean
(
false
)
init
{
scope
.
launch
(
dispatcher
)
{
while
(
isActive
)
{
if
(
needsDisplay
.
getAndSet
(
false
))
{
draw
()
}
yield
()
}
}
}
abstract
fun
draw
()
fun
setNeedsDisplay
()
{
needsDisplay
.
set
(
true
)
}
}
private
fun
test
(
block
:
suspend
TestCoroutineScope
.()
->
Unit
)
=
runBlockingTest
{
pauseDispatcher
()
val
job
=
Job
()
TestCoroutineScope
(
coroutineContext
+
job
).
block
()
job
.
cancel
()
}
}
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment