Commit d12c114d authored by Roman Sedaikin's avatar Roman Sedaikin

Software rendering support.

parent 8f723ef8
......@@ -15,8 +15,10 @@ import kotlin.math.cos
import kotlin.math.sin
fun main(args: Array<String>) {
System.setProperty("skiko.renderApi", "RASTER")
repeat(1) {
createWindow("window $it")
// createWindow("window $it")
SwingSkia()
}
}
......
......@@ -34,5 +34,6 @@ open class SkiaPanel: JLayeredPane {
override fun removeNotify() {
super.removeNotify()
layer.dispose()
}
}
package org.jetbrains.skiko
enum class GraphicsApi {
UNKNOWN, OPENGL, VULKAN, METAL
UNKNOWN, RASTER, OPENGL, D3D, VULKAN, METAL
}
\ No newline at end of file
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.MacOsRedrawer
import org.jetbrains.skiko.redrawer.RasterRedrawer
import org.jetbrains.skiko.redrawer.Redrawer
import org.jetbrains.skiko.redrawer.WindowsRedrawer
import java.awt.Component
......@@ -30,7 +33,10 @@ internal val platformOperations: PlatformOperations by lazy {
return component.graphicsConfiguration.defaultTransform.scaleX.toFloat()
}
override fun createHardwareRedrawer(layer: HardwareLayer) = MacOsRedrawer(layer)
override fun createHardwareRedrawer(layer: HardwareLayer) = when(renderApi) {
GraphicsApi.RASTER -> RasterRedrawer(layer)
else -> MacOsRedrawer(layer)
}
}
OS.Windows -> {
object: PlatformOperations {
......@@ -50,7 +56,10 @@ internal val platformOperations: PlatformOperations by lazy {
return component.graphicsConfiguration.defaultTransform.scaleX.toFloat()
}
override fun createHardwareRedrawer(layer: HardwareLayer) = WindowsRedrawer(layer)
override fun createHardwareRedrawer(layer: HardwareLayer) = when(renderApi) {
GraphicsApi.RASTER -> RasterRedrawer(layer)
else -> WindowsRedrawer(layer)
}
}
}
OS.Linux -> {
......@@ -83,7 +92,10 @@ internal val platformOperations: PlatformOperations by lazy {
// return linuxGetDpiScaleNative(component)
}
override fun createHardwareRedrawer(layer: HardwareLayer) = LinuxRedrawer(layer)
override fun createHardwareRedrawer(layer: HardwareLayer) = when(renderApi) {
GraphicsApi.RASTER -> RasterRedrawer(layer)
else -> LinuxRedrawer(layer)
}
}
}
}
......
......@@ -2,22 +2,10 @@ package org.jetbrains.skiko
import org.jetbrains.skija.*
import org.jetbrains.skiko.redrawer.Redrawer
import org.jetbrains.skiko.context.createContextHandler
import java.awt.Graphics
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 {
suspend fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long)
}
......@@ -25,12 +13,10 @@ interface SkiaRenderer {
private class PictureHolder(val instance: Picture, val width: Int, val height: Int)
open class SkiaLayer : HardwareLayer() {
open val api: GraphicsApi = GraphicsApi.OPENGL
var renderer: SkiaRenderer? = null
val clipComponents = mutableListOf<ClipRectangle>()
private val skijaState = SkijaState()
private val skijaState = createContextHandler(this)
@Volatile
private var isDisposed = false
......@@ -114,26 +100,17 @@ open class SkiaLayer : HardwareLayer() {
override fun draw() {
check(!isDisposed)
if (skijaState.context == null) {
skijaState.context = when (api) {
GraphicsApi.OPENGL -> makeGLContext()
GraphicsApi.METAL -> makeMetalContext()
else -> TODO("Unsupported yet")
}
}
initSkija()
skijaState.apply {
canvas!!.clear(bleachConstant)
initContext()
initCanvas()
clearCanvas()
synchronized(pictureLock) {
val picture = picture
if (picture != null) {
canvas!!.drawPicture(picture.instance)
drawOnCanvas(picture.instance)
}
}
context!!.flush()
flush()
}
}
......@@ -150,51 +127,4 @@ open class SkiaLayer : HardwareLayer() {
true
)
}
private fun initSkija() {
initRenderTarget()
initSurface()
}
private fun initRenderTarget() {
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) {
"RASTER" -> return GraphicsApi.RASTER
"OPENGL" -> return GraphicsApi.OPENGL
"VULKAN" -> return GraphicsApi.VULKAN
"METAL" -> return GraphicsApi.METAL
else -> return GraphicsApi.OPENGL
}
}
internal fun createContextHandler(layer: HardwareLayer): ContextHandler {
return when (renderApi) {
GraphicsApi.RASTER -> RasterContextHandler(layer)
GraphicsApi.OPENGL -> OpenGLContextHandler(layer)
GraphicsApi.METAL -> MetalContextHandler(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()
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() {
if (context == null) {
context = makeMetalContext()
}
}
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() {
if (context == null) {
context = makeGLContext()
}
}
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 RasterContextHandler(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() {
// Raster does not need context
}
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