Unverified Commit 6ad0a4e6 authored by Roman Sedaikin's avatar Roman Sedaikin Committed by GitHub

Merge pull request #56 from JetBrains/raster

Raster
parents 58d59a86 09742091
...@@ -33,6 +33,7 @@ open class SkiaPanel: JLayeredPane { ...@@ -33,6 +33,7 @@ open class SkiaPanel: JLayeredPane {
} }
override fun removeNotify() { override fun removeNotify() {
layer.dispose()
super.removeNotify() super.removeNotify()
} }
} }
package org.jetbrains.skiko package org.jetbrains.skiko
enum class GraphicsApi { enum class GraphicsApi {
UNKNOWN, OPENGL, VULKAN, METAL UNKNOWN, SOFTWARE, OPENGL, DIRECT3D, VULKAN, METAL
} }
\ No newline at end of file
package org.jetbrains.skiko package org.jetbrains.skiko
import org.jetbrains.skiko.GraphicsApi
import org.jetbrains.skiko.context.renderApi
import org.jetbrains.skiko.redrawer.LinuxRedrawer import org.jetbrains.skiko.redrawer.LinuxRedrawer
import org.jetbrains.skiko.redrawer.MacOsRedrawer import org.jetbrains.skiko.redrawer.MacOsRedrawer
import org.jetbrains.skiko.redrawer.RasterRedrawer
import org.jetbrains.skiko.redrawer.Redrawer import org.jetbrains.skiko.redrawer.Redrawer
import org.jetbrains.skiko.redrawer.WindowsRedrawer import org.jetbrains.skiko.redrawer.WindowsRedrawer
import java.awt.Component import java.awt.Component
...@@ -12,7 +15,7 @@ internal interface PlatformOperations { ...@@ -12,7 +15,7 @@ internal interface PlatformOperations {
fun isFullscreen(component: Component): Boolean fun isFullscreen(component: Component): Boolean
fun setFullscreen(component: Component, value: Boolean) fun setFullscreen(component: Component, value: Boolean)
fun getDpiScale(component: Component): Float fun getDpiScale(component: Component): Float
fun createHardwareRedrawer(layer: HardwareLayer): Redrawer fun createRedrawer(layer: HardwareLayer): Redrawer
} }
internal val platformOperations: PlatformOperations by lazy { internal val platformOperations: PlatformOperations by lazy {
...@@ -30,7 +33,10 @@ internal val platformOperations: PlatformOperations by lazy { ...@@ -30,7 +33,10 @@ internal val platformOperations: PlatformOperations by lazy {
return component.graphicsConfiguration.defaultTransform.scaleX.toFloat() return component.graphicsConfiguration.defaultTransform.scaleX.toFloat()
} }
override fun createHardwareRedrawer(layer: HardwareLayer) = MacOsRedrawer(layer) override fun createRedrawer(layer: HardwareLayer) = when(renderApi) {
GraphicsApi.SOFTWARE -> RasterRedrawer(layer)
else -> MacOsRedrawer(layer)
}
} }
OS.Windows -> { OS.Windows -> {
object: PlatformOperations { object: PlatformOperations {
...@@ -50,7 +56,10 @@ internal val platformOperations: PlatformOperations by lazy { ...@@ -50,7 +56,10 @@ internal val platformOperations: PlatformOperations by lazy {
return component.graphicsConfiguration.defaultTransform.scaleX.toFloat() return component.graphicsConfiguration.defaultTransform.scaleX.toFloat()
} }
override fun createHardwareRedrawer(layer: HardwareLayer) = WindowsRedrawer(layer) override fun createRedrawer(layer: HardwareLayer) = when(renderApi) {
GraphicsApi.SOFTWARE -> RasterRedrawer(layer)
else -> WindowsRedrawer(layer)
}
} }
} }
OS.Linux -> { OS.Linux -> {
...@@ -83,7 +92,10 @@ internal val platformOperations: PlatformOperations by lazy { ...@@ -83,7 +92,10 @@ internal val platformOperations: PlatformOperations by lazy {
// return linuxGetDpiScaleNative(component) // return linuxGetDpiScaleNative(component)
} }
override fun createHardwareRedrawer(layer: HardwareLayer) = LinuxRedrawer(layer) override fun createRedrawer(layer: HardwareLayer) = when(renderApi) {
GraphicsApi.SOFTWARE -> RasterRedrawer(layer)
else -> LinuxRedrawer(layer)
}
} }
} }
} }
......
...@@ -2,22 +2,12 @@ package org.jetbrains.skiko ...@@ -2,22 +2,12 @@ package org.jetbrains.skiko
import org.jetbrains.skija.* import org.jetbrains.skija.*
import org.jetbrains.skiko.redrawer.Redrawer import org.jetbrains.skiko.redrawer.Redrawer
import org.jetbrains.skiko.redrawer.RasterRedrawer
import org.jetbrains.skiko.context.createContextHandler
import org.jetbrains.skiko.context.SoftwareContextHandler
import java.awt.Graphics import java.awt.Graphics
import javax.swing.SwingUtilities.isEventDispatchThread import javax.swing.SwingUtilities.isEventDispatchThread
private class SkijaState {
val bleachConstant = if (hostOs == OS.MacOS) 0 else -1
var context: DirectContext? = null
var renderTarget: BackendRenderTarget? = null
var surface: Surface? = null
var canvas: Canvas? = null
fun clear() {
surface?.close()
renderTarget?.close()
}
}
interface SkiaRenderer { interface SkiaRenderer {
suspend fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) suspend fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long)
} }
...@@ -25,12 +15,10 @@ interface SkiaRenderer { ...@@ -25,12 +15,10 @@ interface SkiaRenderer {
private class PictureHolder(val instance: Picture, val width: Int, val height: Int) private class PictureHolder(val instance: Picture, val width: Int, val height: Int)
open class SkiaLayer : HardwareLayer() { open class SkiaLayer : HardwareLayer() {
open val api: GraphicsApi = GraphicsApi.OPENGL
var renderer: SkiaRenderer? = null var renderer: SkiaRenderer? = null
val clipComponents = mutableListOf<ClipRectangle>() val clipComponents = mutableListOf<ClipRectangle>()
private val skijaState = SkijaState() internal var skijaState = createContextHandler(this)
@Volatile @Volatile
private var isDisposed = false private var isDisposed = false
...@@ -43,7 +31,7 @@ open class SkiaLayer : HardwareLayer() { ...@@ -43,7 +31,7 @@ open class SkiaLayer : HardwareLayer() {
override fun init() { override fun init() {
super.init() super.init()
redrawer = platformOperations.createHardwareRedrawer(this) redrawer = platformOperations.createRedrawer(this)
redrawer?.syncSize() redrawer?.syncSize()
needRedraw() needRedraw()
} }
...@@ -114,26 +102,20 @@ open class SkiaLayer : HardwareLayer() { ...@@ -114,26 +102,20 @@ open class SkiaLayer : HardwareLayer() {
override fun draw() { override fun draw() {
check(!isDisposed) check(!isDisposed)
if (skijaState.context == null) {
skijaState.context = when (api) {
GraphicsApi.OPENGL -> makeGLContext()
GraphicsApi.METAL -> makeMetalContext()
else -> TODO("Unsupported yet")
}
}
initSkija()
skijaState.apply { skijaState.apply {
canvas!!.clear(bleachConstant) if (!initContext()) {
fallbackToRaster()
return
}
initCanvas()
clearCanvas()
synchronized(pictureLock) { synchronized(pictureLock) {
val picture = picture val picture = picture
if (picture != null) { if (picture != null) {
canvas!!.drawPicture(picture.instance) drawOnCanvas(picture.instance)
} }
} }
context!!.flush() flush()
} }
} }
...@@ -151,50 +133,11 @@ open class SkiaLayer : HardwareLayer() { ...@@ -151,50 +133,11 @@ open class SkiaLayer : HardwareLayer() {
) )
} }
private fun initSkija() { private fun fallbackToRaster() {
initRenderTarget() println("Falling back to software rendering...")
initSurface() redrawer?.dispose()
} skijaState = SoftwareContextHandler(this)
redrawer = RasterRedrawer(this)
private fun initRenderTarget() { needRedraw()
skijaState.apply {
clear()
val dpi = contentScale
val width = (width * dpi).toInt().coerceAtLeast(0)
val height = (height * dpi).toInt().coerceAtLeast(0)
renderTarget = when (api) {
GraphicsApi.OPENGL -> {
val gl = OpenGLApi.instance
val fbId = gl.glGetIntegerv(gl.GL_DRAW_FRAMEBUFFER_BINDING)
makeGLRenderTarget(
width,
height,
0,
8,
fbId,
FramebufferFormat.GR_GL_RGBA8
)
}
GraphicsApi.METAL -> makeMetalRenderTarget(
width,
height,
0
)
else -> TODO("Unsupported yet")
}
}
}
private fun initSurface() {
skijaState.apply {
surface = Surface.makeFromBackendRenderTarget(
context!!,
renderTarget!!,
SurfaceOrigin.BOTTOM_LEFT,
SurfaceColorFormat.RGBA_8888,
ColorSpace.getSRGB()
)
canvas = surface!!.canvas
}
} }
} }
package org.jetbrains.skiko.context
import org.jetbrains.skija.BackendRenderTarget
import org.jetbrains.skija.Canvas
import org.jetbrains.skija.DirectContext
import org.jetbrains.skija.Picture
import org.jetbrains.skija.Surface
import org.jetbrains.skiko.GraphicsApi
import org.jetbrains.skiko.HardwareLayer
import org.jetbrains.skiko.hostOs
import org.jetbrains.skiko.OS
internal val renderApi: GraphicsApi by lazy {
val environment = System.getenv("SKIKO_RENDER_API")
val property = System.getProperty("skiko.renderApi")
if (environment != null) {
parseRenderApi(environment)
} else {
parseRenderApi(property)
}
}
private fun parseRenderApi(text: String?): GraphicsApi {
when(text) {
"SOFTWARE" -> return GraphicsApi.SOFTWARE
"OPENGL" -> return GraphicsApi.OPENGL
else -> return GraphicsApi.OPENGL
}
}
internal fun createContextHandler(layer: HardwareLayer): ContextHandler {
return when (renderApi) {
GraphicsApi.SOFTWARE -> SoftwareContextHandler(layer)
GraphicsApi.OPENGL -> OpenGLContextHandler(layer)
else -> TODO("Unsupported yet")
}
}
internal abstract class ContextHandler(val layer: HardwareLayer) {
open val bleachConstant = if (hostOs == OS.MacOS) 0 else -1
var context: DirectContext? = null
var renderTarget: BackendRenderTarget? = null
var surface: Surface? = null
var canvas: Canvas? = null
abstract fun initContext(): Boolean
abstract fun initCanvas()
fun clearCanvas() {
canvas?.clear(bleachConstant)
}
open fun drawOnCanvas(picture: Picture) {
canvas?.drawPicture(picture)
}
open fun flush() {
context?.flush()
}
fun dispose() {
surface?.close()
renderTarget?.close()
}
}
\ No newline at end of file
package org.jetbrains.skiko.context
import org.jetbrains.skija.ColorSpace
import org.jetbrains.skija.Surface
import org.jetbrains.skija.SurfaceColorFormat
import org.jetbrains.skija.SurfaceOrigin
import org.jetbrains.skiko.GraphicsApi
import org.jetbrains.skiko.HardwareLayer
import org.jetbrains.skiko.makeMetalContext
import org.jetbrains.skiko.makeMetalRenderTarget
internal class MetalContextHandler(layer: HardwareLayer) : ContextHandler(layer) {
override fun initContext(): Boolean {
try {
if (context == null) {
context = makeMetalContext()
}
} catch (e: Exception) {
println("Failed to create Skia Metal context!")
return false
}
return true
}
override fun initCanvas() {
dispose()
val scale = layer.contentScale
val w = (layer.width * scale).toInt().coerceAtLeast(0)
val h = (layer.height * scale).toInt().coerceAtLeast(0)
renderTarget = makeMetalRenderTarget(w, h, 0)
surface = Surface.makeFromBackendRenderTarget(
context!!,
renderTarget!!,
SurfaceOrigin.BOTTOM_LEFT,
SurfaceColorFormat.RGBA_8888,
ColorSpace.getSRGB()
)
canvas = surface!!.canvas
}
}
package org.jetbrains.skiko.context
import org.jetbrains.skija.ColorSpace
import org.jetbrains.skija.FramebufferFormat
import org.jetbrains.skija.Picture
import org.jetbrains.skija.Surface
import org.jetbrains.skija.SurfaceColorFormat
import org.jetbrains.skija.SurfaceOrigin
import org.jetbrains.skiko.HardwareLayer
import org.jetbrains.skiko.makeGLContext
import org.jetbrains.skiko.makeGLRenderTarget
import org.jetbrains.skiko.OpenGLApi
internal class OpenGLContextHandler(layer: HardwareLayer) : ContextHandler(layer) {
override fun initContext(): Boolean {
try {
if (context == null) {
context = makeGLContext()
}
} catch (e: Exception) {
println("Failed to create Skia OpenGL context!")
return false
}
return true
}
override fun initCanvas() {
dispose()
val scale = layer.contentScale
val w = (layer.width * scale).toInt().coerceAtLeast(0)
val h = (layer.height * scale).toInt().coerceAtLeast(0)
val gl = OpenGLApi.instance
val fbId = gl.glGetIntegerv(gl.GL_DRAW_FRAMEBUFFER_BINDING)
renderTarget = makeGLRenderTarget(
w,
h,
0,
8,
fbId,
FramebufferFormat.GR_GL_RGBA8
)
surface = Surface.makeFromBackendRenderTarget(
context!!,
renderTarget!!,
SurfaceOrigin.BOTTOM_LEFT,
SurfaceColorFormat.RGBA_8888,
ColorSpace.getSRGB()
)
canvas = surface!!.canvas
}
}
package org.jetbrains.skiko.context
import java.awt.AlphaComposite
import java.awt.color.ColorSpace
import java.awt.Color
import java.awt.Graphics2D
import java.awt.Transparency
import java.awt.image.ComponentColorModel
import java.awt.image.BufferedImage
import java.awt.image.DataBuffer
import java.awt.image.DataBufferByte
import java.awt.image.WritableRaster
import java.awt.image.Raster
import org.jetbrains.skija.Bitmap
import org.jetbrains.skija.ColorAlphaType
import org.jetbrains.skija.Canvas
import org.jetbrains.skiko.HardwareLayer
import org.jetbrains.skija.ImageInfo
import org.jetbrains.skija.Picture
internal class SoftwareContextHandler(layer: HardwareLayer) : ContextHandler(layer) {
override val bleachConstant = -1 // it looks like java.awt.Canvas doesn't support transparency
val colorModel = ComponentColorModel(
ColorSpace.getInstance(ColorSpace.CS_sRGB),
true,
false,
Transparency.TRANSLUCENT,
DataBuffer.TYPE_BYTE
)
val storage = Bitmap()
var image: BufferedImage? = null
var imageData: ByteArray? = null
var raster: WritableRaster? = null
override fun initContext(): Boolean {
// Raster does not need context
return true
}
override fun initCanvas() {
dispose()
val scale = layer.contentScale
val w = (layer.width * scale).toInt().coerceAtLeast(0)
val h = (layer.height * scale).toInt().coerceAtLeast(0)
if (storage.getWidth() != w || storage.getHeight() != h) {
storage.allocPixelsFlags(ImageInfo.makeS32(w, h, ColorAlphaType.PREMUL), false)
}
canvas = Canvas(storage)
}
override fun drawOnCanvas(picture: Picture) {
super.drawOnCanvas(picture)
val scale = layer.contentScale
val w = (layer.width * scale).toInt().coerceAtLeast(0)
val h = (layer.height * scale).toInt().coerceAtLeast(0)
val bytes = storage.readPixels(storage.getImageInfo(), (w * 4).toLong(), 0, 0)
if (bytes != null) {
val buffer = DataBufferByte(bytes, bytes.size)
raster = Raster.createInterleavedRaster(
buffer,
w,
h,
w * 4, 4,
intArrayOf(2, 1, 0, 3), // BGRA order
null
)
image = BufferedImage(colorModel, raster!!, false, null)
layer.getGraphics()?.drawImage(image!!, 0, 0, layer.width, layer.height, null)
}
}
override fun flush() {
// Raster does not need to flush canvas
}
}
package org.jetbrains.skiko.redrawer
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.swing.Swing
import org.jetbrains.skiko.FrameDispatcher
import org.jetbrains.skiko.HardwareLayer
internal class RasterRedrawer(
private val layer: HardwareLayer
) : Redrawer {
private val frameDispatcher = FrameDispatcher(Dispatchers.Swing) {
layer.update(System.nanoTime())
layer.draw()
}
override fun dispose() {
frameDispatcher.cancel()
}
override fun needRedraw() {
frameDispatcher.scheduleFrame()
}
}
\ 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