[go: nahoru, domu]

blob: 85803438f72130c39df8c90912cd116c5da584d1 [file] [log] [blame]
Nikolay Igotti65fb0362020-04-07 15:03:05 +03001/*
2 * Copyright 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package androidx.ui.desktop
17
Louis Pullen-Freilich5bb0c4712020-07-20 22:01:15 +010018import androidx.compose.animation.core.AnimationClockObserver
Louis Pullen-Freilich4b2e7e02020-07-21 17:26:40 +010019import androidx.compose.runtime.dispatch.DesktopUiDispatcher
Andrei Rudenko2d7eb402020-06-11 11:11:52 +020020import androidx.ui.text.platform.paragraphActualFactory
21import androidx.ui.text.platform.paragraphIntrinsicsActualFactory
Nikolay Igotti65fb0362020-04-07 15:03:05 +030022import com.jogamp.opengl.GL
23import com.jogamp.opengl.GLAutoDrawable
24import com.jogamp.opengl.GLCapabilities
25import com.jogamp.opengl.GLEventListener
26import com.jogamp.opengl.GLProfile
27import com.jogamp.opengl.awt.GLCanvas
Andrei Rudenko2d7eb402020-06-11 11:11:52 +020028import org.jetbrains.skija.BackendRenderTarget
29import org.jetbrains.skija.Canvas
30import org.jetbrains.skija.ColorSpace
31import org.jetbrains.skija.Context
Igor Demin4a0e9f02020-06-26 17:58:39 +030032import org.jetbrains.skija.FramebufferFormat
33import org.jetbrains.skija.Library
Andrei Rudenko2d7eb402020-06-11 11:11:52 +020034import org.jetbrains.skija.Surface
Igor Demin4a0e9f02020-06-26 17:58:39 +030035import org.jetbrains.skija.SurfaceColorFormat
36import org.jetbrains.skija.SurfaceOrigin
Igor Deminb3f9d002020-07-09 19:29:41 +030037import java.awt.event.KeyAdapter
38import java.awt.event.KeyEvent
39import java.awt.event.MouseAdapter
40import java.awt.event.MouseEvent
41import java.awt.event.MouseMotionAdapter
42import java.nio.IntBuffer
43import javax.swing.JDialog
44import javax.swing.JFrame
Nikolay Igotti65fb0362020-04-07 15:03:05 +030045
46private class SkijaState {
47 var context: Context? = null
48 var renderTarget: BackendRenderTarget? = null
49 var surface: Surface? = null
50 var canvas: Canvas? = null
Nikolay Igotti479275b2020-04-29 11:32:00 +030051 var textureId: Int = 0
52 val intBuf1 = IntBuffer.allocate(1)
Nikolay Igotti65fb0362020-04-07 15:03:05 +030053
54 fun clear() {
55 if (surface != null) {
56 surface!!.close()
57 }
58 if (renderTarget != null) {
59 renderTarget!!.close()
60 }
61 }
62}
63
64interface SkiaRenderer {
65 fun onInit()
66 fun onRender(canvas: Canvas, width: Int, height: Int)
Nikolay Igottie7b9f022020-05-12 22:47:57 +030067 fun onReshape(canvas: Canvas, width: Int, height: Int)
Nikolay Igotti65fb0362020-04-07 15:03:05 +030068 fun onDispose()
Nikolay Igotti40e54122020-05-11 21:30:52 +030069
70 fun onMouseClicked(x: Int, y: Int, modifiers: Int)
71 fun onMousePressed(x: Int, y: Int, modifiers: Int)
72 fun onMouseReleased(x: Int, y: Int, modifiers: Int)
73 fun onMouseDragged(x: Int, y: Int, modifiers: Int)
Nikolay Igotti17417392020-05-15 14:56:57 +030074
75 fun onKeyTyped(char: Char)
76 fun onKeyPressed(code: Int, char: Char)
77 fun onKeyReleased(code: Int, char: Char)
Nikolay Igotti65fb0362020-04-07 15:03:05 +030078}
79
Roman Sedaikineb45e98b2020-06-26 13:17:03 +030080class Window : JFrame, SkiaFrame {
Nikolay Igotti65fb0362020-04-07 15:03:05 +030081 companion object {
82 init {
Igor Deminb3f9d002020-07-09 19:29:41 +030083 initCompose()
Nikolay Igotti65fb0362020-04-07 15:03:05 +030084 }
85 }
86
Roman Sedaikineb45e98b2020-06-26 13:17:03 +030087 override val parent: AppFrame
88 override val glCanvas: GLCanvas
Roman Sedaikineb45e98b2020-06-26 13:17:03 +030089 override var renderer: SkiaRenderer? = null
Andrey Rudenko6921eaa2020-07-03 13:41:17 +020090 override val vsync = true
Nikolay Igotti65fb0362020-04-07 15:03:05 +030091
Roman Sedaikineb45e98b2020-06-26 13:17:03 +030092 constructor(width: Int, height: Int, parent: AppFrame) : super() {
93 this.parent = parent
94 setSize(width, height)
95 }
Nikolay Igotti65fb0362020-04-07 15:03:05 +030096
97 init {
Roman Sedaikineb45e98b2020-06-26 13:17:03 +030098 glCanvas = initCanvas(this, vsync)
Nikolay Igotti65fb0362020-04-07 15:03:05 +030099 glCanvas.setSize(width, height)
Nikolay Igotti65fb0362020-04-07 15:03:05 +0300100 contentPane.add(glCanvas)
101 size = contentPane.preferredSize
102 }
Roman Sedaikineb45e98b2020-06-26 13:17:03 +0300103}
104
105class Dialog : JDialog, SkiaFrame {
Louis Pullen-Freilicha2be36b2020-07-21 21:35:11 +0100106 @OptIn(androidx.compose.ui.text.android.InternalPlatformTextApi::class)
Roman Sedaikineb45e98b2020-06-26 13:17:03 +0300107 companion object {
108 init {
109 Library.load("/", "skija")
110 // Until https://github.com/Kotlin/kotlinx.coroutines/issues/2039 is resolved
111 // we have to set this property manually for coroutines to work.
112 System.getProperties().setProperty("kotlinx.coroutines.fast.service.loader", "false")
113
114 @Suppress("DEPRECATION_ERROR")
115 paragraphIntrinsicsActualFactory = ::DesktopParagraphIntrinsics
116 @Suppress("DEPRECATION_ERROR")
117 paragraphActualFactory = ::DesktopParagraph
118 }
119 }
120
121 override val parent: AppFrame
122 override val glCanvas: GLCanvas
Roman Sedaikineb45e98b2020-06-26 13:17:03 +0300123 override var renderer: SkiaRenderer? = null
Andrey Rudenko6921eaa2020-07-03 13:41:17 +0200124 override val vsync = true
Roman Sedaikineb45e98b2020-06-26 13:17:03 +0300125
126 constructor(
127 attached: JFrame?,
128 width: Int,
129 height: Int,
130 parent: AppFrame
131 ) : super(attached, true) {
132 this.parent = parent
133 setSize(width, height)
134 }
135
136 init {
137 glCanvas = initCanvas(this, vsync)
138 glCanvas.setSize(width, height)
139 contentPane.add(glCanvas)
140 size = contentPane.preferredSize
141 }
142}
143
144internal interface SkiaFrame {
145 val parent: AppFrame
146 val glCanvas: GLCanvas
Roman Sedaikineb45e98b2020-06-26 13:17:03 +0300147 var renderer: SkiaRenderer?
148 val vsync: Boolean
Nikolay Igotti65fb0362020-04-07 15:03:05 +0300149
Roman Sedaikineb45e98b2020-06-26 13:17:03 +0300150 fun close() {
151 glCanvas.destroy()
152 }
153}
154
155private fun initSkija(
156 glCanvas: GLCanvas,
157 skijaState: SkijaState,
158 vsync: Boolean,
159 reinitTexture: Boolean
160) {
161 with(skijaState) {
162 val width = glCanvas.width
163 val height = glCanvas.height
164 val dpiX = glCanvas.nativeSurface.surfaceWidth.toFloat() / width
165 val dpiY = glCanvas.nativeSurface.surfaceHeight.toFloat() / height
166 if (vsync) glCanvas.gl.setSwapInterval(1)
167 skijaState.clear()
168 val intBuf1 = IntBuffer.allocate(1)
169 glCanvas.gl.glGetIntegerv(GL.GL_DRAW_FRAMEBUFFER_BINDING, intBuf1)
170 val fbId = intBuf1[0]
171 renderTarget = BackendRenderTarget.makeGL(
172 (width * dpiX).toInt(),
173 (height * dpiY).toInt(),
174 0,
175 8,
176 fbId,
177 FramebufferFormat.GR_GL_RGBA8
178 )
179 surface = Surface.makeFromBackendRenderTarget(
180 context,
181 renderTarget,
182 SurfaceOrigin.BOTTOM_LEFT,
183 SurfaceColorFormat.RGBA_8888,
184 ColorSpace.getSRGB()
185 )
186 canvas = surface!!.canvas
187 canvas!!.scale(dpiX, dpiY)
188 if (reinitTexture) {
189 glCanvas.gl.glGetIntegerv(GL.GL_TEXTURE_BINDING_2D, intBuf1)
190 skijaState.textureId = intBuf1[0]
Nikolay Igotti65fb0362020-04-07 15:03:05 +0300191 }
192 }
193}
Roman Sedaikineb45e98b2020-06-26 13:17:03 +0300194
Andrey Rudenko6921eaa2020-07-03 13:41:17 +0200195// Simple FPS tracker for debug purposes
196internal class FPSTracker {
197 private var t0 = 0L
198 private val times = DoubleArray(155)
199 private var timesIdx = 0
200
201 fun track() {
202 val t1 = System.nanoTime()
203 times[timesIdx] = (t1 - t0) / 1000000.0
204 t0 = t1
205 timesIdx = (timesIdx + 1) % times.size
206 println("FPS: ${1000 / times.takeWhile { it > 0 }.average()}")
207 }
208}
209
Roman Sedaikineb45e98b2020-06-26 13:17:03 +0300210private fun initCanvas(frame: SkiaFrame, vsync: Boolean = false): GLCanvas {
211 val profile = GLProfile.get(GLProfile.GL3)
212 val capabilities = GLCapabilities(profile)
213 // We cannot rely on double buffering.
214 capabilities.doubleBuffered = false
215 val glCanvas = GLCanvas(capabilities)
216
217 val skijaState = SkijaState()
218
219 glCanvas.addMouseListener(object : MouseAdapter() {
220 override fun mouseClicked(event: MouseEvent) {
221 frame.renderer!!.onMouseClicked(event.x, event.y, event.getModifiersEx())
222 }
Igor Deminb3f9d002020-07-09 19:29:41 +0300223
Roman Sedaikineb45e98b2020-06-26 13:17:03 +0300224 override fun mousePressed(event: MouseEvent) {
225 frame.renderer!!.onMousePressed(event.x, event.y, event.getModifiersEx())
226 }
Igor Deminb3f9d002020-07-09 19:29:41 +0300227
Roman Sedaikineb45e98b2020-06-26 13:17:03 +0300228 override fun mouseReleased(event: MouseEvent) {
229 frame.renderer!!.onMouseReleased(event.x, event.y, event.getModifiersEx())
230 }
231 })
232 glCanvas.addMouseMotionListener(object : MouseMotionAdapter() {
233 override fun mouseDragged(event: MouseEvent) {
234 frame.renderer!!.onMouseDragged(event.x, event.y, event.getModifiersEx())
235 }
236 })
237 glCanvas.addKeyListener(object : KeyAdapter() {
238 override fun keyPressed(event: KeyEvent) {
239 frame.renderer!!.onKeyPressed(event.keyCode, event.keyChar)
240 }
Igor Deminb3f9d002020-07-09 19:29:41 +0300241
Roman Sedaikineb45e98b2020-06-26 13:17:03 +0300242 override fun keyReleased(event: KeyEvent) {
243 frame.renderer!!.onKeyReleased(event.keyCode, event.keyChar)
244 }
Igor Deminb3f9d002020-07-09 19:29:41 +0300245
Roman Sedaikineb45e98b2020-06-26 13:17:03 +0300246 override fun keyTyped(event: KeyEvent) {
247 frame.renderer!!.onKeyTyped(event.keyChar)
248 }
249 })
250 glCanvas.addGLEventListener(object : GLEventListener {
251 override fun reshape(
252 drawable: GLAutoDrawable?,
253 x: Int,
254 y: Int,
255 width: Int,
256 height: Int
257 ) {
258 initSkija(glCanvas, skijaState, vsync, false)
259 frame.renderer!!.onReshape(skijaState.canvas!!, width, height)
260 }
Igor Deminb3f9d002020-07-09 19:29:41 +0300261
Roman Sedaikineb45e98b2020-06-26 13:17:03 +0300262 override fun init(drawable: GLAutoDrawable?) {
263 skijaState.context = Context.makeGL()
264 initSkija(glCanvas, skijaState, vsync, false)
265 frame.renderer!!.onInit()
266 }
Igor Deminb3f9d002020-07-09 19:29:41 +0300267
Roman Sedaikineb45e98b2020-06-26 13:17:03 +0300268 override fun dispose(drawable: GLAutoDrawable?) {
269 frame.renderer!!.onDispose()
270 AppManager.removeWindow(frame.parent)
271 }
Igor Deminb3f9d002020-07-09 19:29:41 +0300272
Andrey Rudenko6921eaa2020-07-03 13:41:17 +0200273 override fun display(drawable: GLAutoDrawable) {
Roman Sedaikineb45e98b2020-06-26 13:17:03 +0300274 skijaState.apply {
Andrey Rudenko6921eaa2020-07-03 13:41:17 +0200275 val gl = drawable.gl!!
Roman Sedaikineb45e98b2020-06-26 13:17:03 +0300276 canvas!!.clear(0xFFFFFFF)
277 gl.glBindTexture(GL.GL_TEXTURE_2D, textureId)
278 frame.renderer!!.onRender(
279 canvas!!, glCanvas.width, glCanvas.height
280 )
281 context!!.flush()
282 gl.glGetIntegerv(GL.GL_TEXTURE_BINDING_2D, intBuf1)
283 textureId = intBuf1[0]
284 if (vsync) gl.glFinish()
285 }
286 }
287 })
288
289 return glCanvas
Andrey Rudenko6921eaa2020-07-03 13:41:17 +0200290}
291
292internal class DesktopAnimationClock(fps: Int, val dispatcher: DesktopUiDispatcher) :
293 BaseAnimationClock() {
294 val delay = 1_000 / fps
295
296 @Volatile
297 private var scheduled = false
298 private fun frameCallback(time: Long) {
299 scheduled = false
300 dispatchTime(time / 1000000)
301 }
302
303 override fun subscribe(observer: AnimationClockObserver) {
304 super.subscribe(observer)
305 scheduleIfNeeded()
306 }
307
308 override fun dispatchTime(frameTimeMillis: Long) {
309 super.dispatchTime(frameTimeMillis)
310 scheduleIfNeeded()
311 }
312
313 private fun scheduleIfNeeded() {
314 when {
315 scheduled -> return
316 !hasObservers() -> return
317 else -> {
318 scheduled = true
319 dispatcher.scheduleCallbackWithDelay(delay, ::frameCallback)
320 }
321 }
322 }
323}