From 06bf48a24ea71681a5be12849f3afea027cd9b8e Mon Sep 17 00:00:00 2001 From: Carlos Ballesteros Velasco Date: Thu, 24 Mar 2022 20:17:48 +0100 Subject: [PATCH] Initial GPU vector rendering as a sample (#524) --- build.gradle.kts | 4 + .../kotlin/com/soywiz/kmem/Numbers.kt | 4 +- .../com/soywiz/korge/gradle/KorgeExtension.kt | 4 + korge-sandbox/src/commonMain/kotlin/Main.kt | 3 +- .../kotlin/MainGpuVectorRendering.kt | 312 ++++++++ .../resources/Ghostscript_Tiger.svg | 725 ++++++++++++++++++ .../com/soywiz/korge/render/BatchBuilder2D.kt | 35 +- .../com/soywiz/korge/render/RenderContext.kt | 33 +- .../soywiz/korge/view/ViewRenderToBitmap.kt | 2 +- .../soywiz/korge/view/ViewsOpenglJvmTest.kt | 30 + ...lFilterIdentityDefaultRenderBufferSize.log | 2 - ...vmTestOpenglRenderToTextureWithStencil.log | 318 ++++++++ .../kotlin/com/soywiz/kgl/IKmlGl.kt | 1 + .../commonMain/kotlin/com/soywiz/kgl/KmlGl.kt | 3 + .../kotlin/com/soywiz/kgl/KmlGlProxy.kt | 11 + .../commonMain/kotlin/com/soywiz/korag/AG.kt | 64 +- .../kotlin/com/soywiz/korag/DefaultShaders.kt | 23 + .../kotlin/com/soywiz/korag/OpenglAG.kt | 49 +- .../kotlin/com/soywiz/korag/log/LogAG.kt | 7 +- .../kotlin/com/soywiz/kgl/KmlGlJsCanvas.kt | 8 + .../com/soywiz/korgw/awt/AwtGameWindow.kt | 32 +- .../com/soywiz/korgw/awt/BaseAwtGameWindow.kt | 3 - .../kotlin/com/soywiz/korgw/osx/Cocoa.kt | 53 +- .../com/soywiz/korgw/osx/MacosGLContext.kt | 271 +++++++ .../com/soywiz/korgw/osx/MacosGameWindow.kt | 22 +- .../korgw/platform/BaseOpenglContext.kt | 75 +- .../com/soywiz/korgw/platform/INativeGL.kt | 586 +++++--------- .../com/soywiz/korgw/platform/NativeKgl.kt | 13 +- .../kotlin/com/soywiz/kgl/KmlGlNative.kt | 1 + .../kotlin/com/soywiz/korim/color/RGBAf.kt | 21 + .../kotlin/com/soywiz/korim/paint/Filler.kt | 48 +- .../kotlin/com/soywiz/korim/paint/Paint.kt | 33 +- .../com/soywiz/korim/vector/CycleMethod.kt | 4 +- .../kotlin/com/soywiz/korim/vector/Shape.kt | 17 +- .../com/soywiz/korim/vector/ShapeBuilder.kt | 5 +- .../kotlin/com/soywiz/korio/dynamic/Dyn.kt | 7 +- .../kotlin/com/soywiz/korio/dynamic/DynApi.kt | 2 + .../com/soywiz/korio/dynamic/DynApiJvm.kt | 60 +- .../com/soywiz/korma/geom/shape/Shape2d.kt | 3 + 39 files changed, 2301 insertions(+), 593 deletions(-) create mode 100644 korge-sandbox/src/commonMain/kotlin/MainGpuVectorRendering.kt create mode 100644 korge-sandbox/src/commonMain/resources/Ghostscript_Tiger.svg create mode 100644 korge/src/jvmTest/resources/korge/render/ViewsJvmTestOpenglRenderToTextureWithStencil.log create mode 100644 korgw/src/jvmMain/kotlin/com/soywiz/korgw/osx/MacosGLContext.kt diff --git a/build.gradle.kts b/build.gradle.kts index 7c8e49b22..e3717430e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -99,6 +99,10 @@ val javaAddOpens = ArrayList().apply { add("--add-opens=java.desktop/sun.java2d.opengl=ALL-UNNAMED") add("--add-opens=java.desktop/java.awt=ALL-UNNAMED") add("--add-opens=java.desktop/sun.awt=ALL-UNNAMED") + if (isMacos) { + add("--add-opens=java.desktop/sun.lwawt.macosx=ALL-UNNAMED") + add("--add-opens=java.desktop/sun.lwawt=ALL-UNNAMED") + } if (isLinux) add("--add-opens=java.desktop/sun.awt.X11=ALL-UNNAMED") }.toTypedArray() diff --git a/kmem/src/commonMain/kotlin/com/soywiz/kmem/Numbers.kt b/kmem/src/commonMain/kotlin/com/soywiz/kmem/Numbers.kt index 383f7c669..3cc0ac26d 100644 --- a/kmem/src/commonMain/kotlin/com/soywiz/kmem/Numbers.kt +++ b/kmem/src/commonMain/kotlin/com/soywiz/kmem/Numbers.kt @@ -25,9 +25,9 @@ public fun Float.toIntFloor(): Int = floor(this).toInt() public fun Double.toIntFloor(): Int = floor(this).toInt() /** Converts [this] into [Int] rounding to the nearest */ -public fun Float.toIntRound(): Int = round(this).toLong().toInt() +public fun Float.toIntRound(): Int = round(this).toInt() /** Converts [this] into [Int] rounding to the nearest */ -public fun Double.toIntRound(): Int = round(this).toLong().toInt() +public fun Double.toIntRound(): Int = round(this).toInt() /** Convert this [Long] into an [Int] but throws an [IllegalArgumentException] in the case that operation would produce an overflow */ public fun Long.toIntSafe(): Int = if (this in Int.MIN_VALUE.toLong()..Int.MAX_VALUE.toLong()) this.toInt() else throw IllegalArgumentException("Long doesn't fit Integer") diff --git a/korge-gradle-plugin/src/main/kotlin/com/soywiz/korge/gradle/KorgeExtension.kt b/korge-gradle-plugin/src/main/kotlin/com/soywiz/korge/gradle/KorgeExtension.kt index 37cc7754b..c327162dc 100644 --- a/korge-gradle-plugin/src/main/kotlin/com/soywiz/korge/gradle/KorgeExtension.kt +++ b/korge-gradle-plugin/src/main/kotlin/com/soywiz/korge/gradle/KorgeExtension.kt @@ -298,6 +298,10 @@ class KorgeExtension(val project: Project) { add("--add-opens=java.desktop/sun.java2d.opengl=ALL-UNNAMED") add("--add-opens=java.desktop/java.awt=ALL-UNNAMED") add("--add-opens=java.desktop/sun.awt=ALL-UNNAMED") + if (isMacos) { + add("--add-opens=java.desktop/sun.lwawt=ALL-UNNAMED") + add("--add-opens=java.desktop/sun.lwawt.macosx=ALL-UNNAMED") + } if (isLinux) add("--add-opens=java.desktop/sun.awt.X11=ALL-UNNAMED") } diff --git a/korge-sandbox/src/commonMain/kotlin/Main.kt b/korge-sandbox/src/commonMain/kotlin/Main.kt index bcdd9d5b7..2d1aebea1 100644 --- a/korge-sandbox/src/commonMain/kotlin/Main.kt +++ b/korge-sandbox/src/commonMain/kotlin/Main.kt @@ -32,8 +32,9 @@ import kotlin.random.* suspend fun main() = Korge(bgcolor = Colors.DARKCYAN.mix(Colors.BLACK, 0.8), clipBorders = false //, debugAg = true ) { + mainGpuVectorRendering() //mainFiltersRenderToBitmap() - mainBlur() + //mainBlur() //mainCustomSolidRectShader() //mainMipmaps() //mainColorTransformFilter() diff --git a/korge-sandbox/src/commonMain/kotlin/MainGpuVectorRendering.kt b/korge-sandbox/src/commonMain/kotlin/MainGpuVectorRendering.kt new file mode 100644 index 000000000..c9faef2c6 --- /dev/null +++ b/korge-sandbox/src/commonMain/kotlin/MainGpuVectorRendering.kt @@ -0,0 +1,312 @@ +import com.soywiz.kds.* +import com.soywiz.klogger.* +import com.soywiz.kmem.* +import com.soywiz.korag.* +import com.soywiz.korag.shader.* +import com.soywiz.korge.render.* +import com.soywiz.korge.view.* +import com.soywiz.korge.view.BlendMode +import com.soywiz.korim.bitmap.* +import com.soywiz.korim.color.* +import com.soywiz.korim.font.* +import com.soywiz.korim.format.* +import com.soywiz.korim.paint.* +import com.soywiz.korim.text.* +import com.soywiz.korim.vector.* +import com.soywiz.korim.vector.format.* +import com.soywiz.korio.file.std.* +import com.soywiz.korma.geom.* +import com.soywiz.korma.geom.shape.* +import com.soywiz.korma.geom.vector.* + +suspend fun Stage.mainGpuVectorRendering() { + Console.log("[1]") + val korgeBitmap = resourcesVfs["korge.png"].readBitmap() + Console.log("[2]") + val tigerSvg = resourcesVfs["Ghostscript_Tiger.svg"].readSVG() + Console.log("[3]") + //AudioData(44100, AudioSamples(1, 1024)).toSound().play() + + fun Context2d.buildGraphics() { + keep { + draw(tigerSvg) + translate(100, 200) + fill(Colors.BLUE) { + rect(-10, -10, 120, 120) + rectHole(40, 40, 80, 80) + } + fill(Colors.YELLOW) { + this.circle(100, 100, 40) + //rect(-100, -100, 500, 500) + //rectHole(40, 40, 320, 320) + } + fill(Colors.RED) { + regularPolygon(6, 30.0, x = 100.0, y = 100.0) + //rect(-100, -100, 500, 500) + //rectHole(40, 40, 320, 320) + } + } + keep { + translate(100, 20) + scale(2.0) + globalAlpha = 0.75 + fillStyle = BitmapPaint( + korgeBitmap, + Matrix().translate(50, 50).scale(0.125), + cycleX = CycleMethod.REPEAT, + cycleY = CycleMethod.REPEAT + ) + fillRect(0.0, 0.0, 100.0, 100.0) + fillStyle = + createLinearGradient(0.0, 0.0, 200.0, 200.0, transform = Matrix().scale(0.5).pretranslate(130, 30)) + .addColorStop(0.0, Colors.RED) + .addColorStop(1.0, Colors.BLUE) + fillRect(100.0, 0.0, 100.0, 100.0) + } + keep { + font = DefaultTtfFont + fontSize = 16.0 + fillStyle = Colors.WHITE + alignment = TextAlignment.TOP_LEFT + fillText("HELLO WORLD", 0.0, 16.0) + } + } + + gpuShapeView { buildGraphics() }.xy(0, 0)//.rotation(45.degrees) + image(NativeImage(512, 512).context2d { buildGraphics() }).xy(700, 0) + image(Bitmap32(512, 512).context2d { buildGraphics() }).xy(700, 370) +} + +inline fun Container.gpuShapeView(buildContext2d: Context2d.() -> Unit) = + GpuShapeView(buildShape { buildContext2d() }).addTo(this) + +inline fun Container.gpuShapeView(shape: Shape, callback: @ViewDslMarker GpuShapeView.() -> Unit = {}) = + GpuShapeView(shape).addTo(this, callback) + +class GpuShapeView(shape: Shape) : View() { + private val pointCache = FastIdentityMap() + + var shape: Shape = shape + set(value) { + field = value + pointCache.clear() + } + + companion object { + val u_Color = Uniform("u_Color", VarType.Float4) + val u_Transform = Uniform("u_Transform", VarType.Mat4) + val LAYOUT = VertexLayout(DefaultShaders.a_Pos) + val LAYOUT_FILL = VertexLayout(DefaultShaders.a_Pos, DefaultShaders.a_Tex) + val VERTEX_FILL = VertexShaderDefault { + SET(out, u_ProjMat * u_ViewMat * vec4(a_Pos, 0f.lit, 1f.lit)) + SET(v_Tex, DefaultShaders.a_Tex) + } + val PROGRAM_STENCIL = Program( + vertex = VertexShaderDefault { SET(out, u_ProjMat * u_ViewMat * vec4(a_Pos, 0f.lit, 1f.lit)) }, + fragment = FragmentShaderDefault { SET(out, vec4(1f.lit, 0f.lit, 0f.lit, 1f.lit)) }, + ) + val PROGRAM_COLOR = Program( + vertex = VERTEX_FILL, + fragment = FragmentShaderDefault { + SET(out, u_Color) + }, + ) + val PROGRAM_BITMAP = Program( + vertex = VERTEX_FILL, + fragment = FragmentShaderDefault { + // @TODO: we should convert 0..1 to texture slice coordinates + SET(out, texture2D(u_Tex, fract(vec2((vec4(v_Tex, 0f.lit, 1f.lit) * u_Transform)["xy"])))) + }, + ) + val PROGRAM_LINEAR_GRADIENT = Program( + vertex = VERTEX_FILL, + fragment = FragmentShaderDefault { + SET(out, texture2D(u_Tex, (vec4(v_Tex.x, v_Tex.y, 0f.lit, 1f.lit) * u_Transform)["xy"])) + }, + ) + } + + private val bb = BoundsBuilder() + override fun getLocalBoundsInternal(out: Rectangle) { + shape.getBounds(out, bb) + } + + var msaaSamples: Int = 4 + + override fun renderInternal(ctx: RenderContext) { + ctx.flush() + val currentRenderBuffer = ctx.ag.currentRenderBufferOrMain + ctx.renderToTexture(currentRenderBuffer.width, currentRenderBuffer.height, { + renderShape(ctx, shape) + }, hasStencil = true, msamples = msaaSamples) { texture -> + ctx.useBatcher { + it.drawQuad(texture, x = 0f, y = 0f) + } + } + } + + private fun renderShape(ctx: RenderContext, shape: Shape) { + when (shape) { + EmptyShape -> Unit + is FillShape -> renderShape(ctx, shape) + is CompoundShape -> for (v in shape.components) renderShape(ctx, v) + is PolylineShape -> { + //println("TODO: PolylineShape not implemented. Convert into fills") + } + is TextShape -> renderShape(ctx, shape.primitiveShapes) + else -> TODO("shape=$shape") + } + } + + private fun renderShape(ctx: RenderContext, shape: FillShape) { + val path = shape.path + val m = globalMatrix + + val points = pointCache.getOrPut(path) { + val points = PointArrayList() + path.emitPoints2 { x, y, move -> + points.add(x, y) + } + points + } + + val bb = BoundsBuilder() + bb.reset() + + val data = FloatArray(points.size * 2 + 4) + for (n in 0 until points.size + 1) { + val x = points.getX(n % points.size).toFloat() + val y = points.getY(n % points.size).toFloat() + val tx = m.transformXf(x, y) + val ty = m.transformYf(x, y) + data[(n + 1) * 2 + 0] = tx + data[(n + 1) * 2 + 1] = ty + bb.add(tx, ty) + } + data[0] = ((bb.xmax + bb.xmin) / 2).toFloat() + data[1] = ((bb.ymax + bb.ymin) / 2).toFloat() + + if (shape.path.winding != Winding.EVEN_ODD) { + error("Currently only supported EVEN_ODD winding") + } + + val bounds = bb.getBounds() + + ctx.dynamicVertexBufferPool { vertices -> + vertices.upload(data) + ctx.batch.updateStandardUniforms() + + ctx.batch.simulateBatchStats(points.size + 2) + + val scissor: AG.Scissor? = AG.Scissor().setTo( + //bounds + Rectangle.fromBounds(bounds.left.toInt(), bounds.top.toInt(), bounds.right.toIntCeil(), bounds.bottom.toIntCeil()) + ) + //val scissor: AG.Scissor? = null + + ctx.ag.clearStencil(0, scissor = scissor) + //ctx.ag.clearStencil(0, scissor = null) + ctx.ag.draw( + vertices = vertices, + program = PROGRAM_STENCIL, + type = AG.DrawType.TRIANGLE_FAN, + vertexLayout = LAYOUT, + vertexCount = points.size + 2, + uniforms = ctx.batch.uniforms, + stencil = AG.StencilState( + enabled = true, + readMask = 0xFF, + compareMode = AG.CompareMode.ALWAYS, + referenceValue = 0xFF, + writeMask = 0xFF, + actionOnDepthFail = AG.StencilOp.KEEP, + actionOnDepthPassStencilFail = AG.StencilOp.KEEP, + actionOnBothPass = AG.StencilOp.INVERT, + ), + blending = BlendMode.NONE.factors, + colorMask = AG.ColorMaskState(false, false, false, false), + scissor = scissor, + ) + } + renderFill(ctx, shape.paint, shape.transform, bounds) + } + + private val colorUniforms = AG.UniformValues() + private val bitmapUniforms = AG.UniformValues() + private val gradientUniforms = AG.UniformValues() + private val gradientBitmap = Bitmap32(256, 1) + + private val colorF = FloatArray(4) + + private fun renderFill(ctx: RenderContext, paint: Paint, transform: Matrix, bounds: Rectangle) { + if (paint is NonePaint) return + + ctx.dynamicVertexBufferPool { vertices -> + val data = FloatArray(4 * 4) + var n = 0 + val x0 = bounds.left.toFloat() + val y0 = bounds.top.toFloat() + val x1 = bounds.right.toFloat() + val y1 = bounds.bottom.toFloat() + val w = x1 - x0 + val h = y1 - y0 + data[n++] = x0; data[n++] = y0; data[n++] = 0f; data[n++] = 0f + data[n++] = x1; data[n++] = y0; data[n++] = w; data[n++] = 0f + data[n++] = x1; data[n++] = y1; data[n++] = w; data[n++] = h + data[n++] = x0; data[n++] = y1; data[n++] = 0f; data[n++] = h + + vertices.upload(data) + ctx.useBatcher { batch -> + batch.updateStandardUniforms() + var uniforms: AG.UniformValues = colorUniforms + var program: Program = PROGRAM_COLOR + when (paint) { + is ColorPaint -> { + val color = paint + color.writeFloat(colorF) + colorUniforms[u_Color] = colorF + program = PROGRAM_COLOR + uniforms = colorUniforms + } + is BitmapPaint -> { + bitmapUniforms[DefaultShaders.u_Tex] = AG.TextureUnit(ctx.getTex(paint.bitmap).base) + val mat = (paint.transform * transform) + mat.scale(1.0 / paint.bitmap.width, 1.0 / paint.bitmap.height) + bitmapUniforms[u_Transform] = mat.toMatrix3D() + program = PROGRAM_BITMAP + uniforms = bitmapUniforms + } + is GradientPaint -> { + paint.fillColors(gradientBitmap.dataPremult) + gradientUniforms[DefaultShaders.u_Tex] = AG.TextureUnit(ctx.getTex(gradientBitmap).base) + val mat = paint.transform * transform * paint.gradientMatrixInv + gradientUniforms[u_Transform] = mat.toMatrix3D() + program = PROGRAM_LINEAR_GRADIENT + uniforms = gradientUniforms + } + else -> { + TODO("paint=$paint") + } + } + batch.setTemporalUniforms(uniforms) { + ctx.batch.simulateBatchStats(4) + ctx.ag.draw( + vertices = vertices, + program = program, + type = AG.DrawType.TRIANGLE_FAN, + vertexLayout = LAYOUT_FILL, + vertexCount = 4, + uniforms = ctx.batch.uniforms, + stencil = AG.StencilState( + enabled = true, + compareMode = AG.CompareMode.NOT_EQUAL, + writeMask = 0, + ), + blending = BlendMode.NORMAL.factors, + colorMask = AG.ColorMaskState(true, true, true, true), + ) + } + } + } + } +} diff --git a/korge-sandbox/src/commonMain/resources/Ghostscript_Tiger.svg b/korge-sandbox/src/commonMain/resources/Ghostscript_Tiger.svg new file mode 100644 index 000000000..679edec2e --- /dev/null +++ b/korge-sandbox/src/commonMain/resources/Ghostscript_Tiger.svg @@ -0,0 +1,725 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/korge/src/commonMain/kotlin/com/soywiz/korge/render/BatchBuilder2D.kt b/korge/src/commonMain/kotlin/com/soywiz/korge/render/BatchBuilder2D.kt index acb1fa0e5..82cff9203 100644 --- a/korge/src/commonMain/kotlin/com/soywiz/korge/render/BatchBuilder2D.kt +++ b/korge/src/commonMain/kotlin/com/soywiz/korge/render/BatchBuilder2D.kt @@ -85,6 +85,7 @@ class BatchBuilder2D constructor( var vertexCount: Int get() = _vertexCount + //set(value) { _vertexCount = value } internal set(value) { _vertexCount = value } @PublishedApi internal var vertexPos = 0 @@ -880,6 +881,16 @@ class BatchBuilder2D constructor( //resetCachedState() } + fun simulateBatchStats(vertexCount: Int) { + val oldVertexCount = this.vertexCount + this.vertexCount = vertexCount + try { + beforeFlush(this) + } finally { + this.vertexCount = oldVertexCount + } + } + /** * Executes [callback] while setting temporarily the view matrix to [matrix] */ @@ -932,25 +943,27 @@ class BatchBuilder2D constructor( } @PublishedApi - internal val tempOldUniforms = AG.UniformValues() + internal val tempOldUniformsList: Pool = Pool { AG.UniformValues() } /** * Executes [callback] while setting temporarily a set of [uniforms] */ inline fun setTemporalUniforms(uniforms: AG.UniformValues?, callback: () -> Unit) { - if (uniforms != null && uniforms.isNotEmpty()) { - flush() - tempOldUniforms.setTo(this.uniforms) - this.uniforms.put(uniforms) - } - try { - callback() - } finally { + tempOldUniformsList { tempOldUniforms -> if (uniforms != null && uniforms.isNotEmpty()) { flush() - this.uniforms.setTo(tempOldUniforms) + tempOldUniforms.setTo(this.uniforms) + this.uniforms.put(uniforms) } - } + try { + callback() + } finally { + if (uniforms != null && uniforms.isNotEmpty()) { + flush() + this.uniforms.setTo(tempOldUniforms) + } + } + } } } diff --git a/korge/src/commonMain/kotlin/com/soywiz/korge/render/RenderContext.kt b/korge/src/commonMain/kotlin/com/soywiz/korge/render/RenderContext.kt index da2b359c2..a090f53c8 100644 --- a/korge/src/commonMain/kotlin/com/soywiz/korge/render/RenderContext.kt +++ b/korge/src/commonMain/kotlin/com/soywiz/korge/render/RenderContext.kt @@ -57,6 +57,10 @@ class RenderContext constructor( @KorgeInternal val batch = BatchBuilder2D(this, batchMaxQuads) + val dynamicVertexBufferPool = Pool { ag.createVertexBuffer() } + val dynamicVertexDataPool = Pool { ag.createVertexData() } + val dynamicIndexBufferPool = Pool { ag.createIndexBuffer() } + @OptIn(KorgeInternal::class) inline fun useBatcher(block: (BatchBuilder2D) -> Unit) = batch.use(block) @@ -107,7 +111,12 @@ class RenderContext constructor( * This method is useful for per-frame filters. If you plan to keep the texture data, consider using the [renderToBitmap] method. */ @OptIn(KorgeInternal::class) - inline fun renderToTexture(width: Int, height: Int, render: (AG.RenderBuffer) -> Unit, use: (texture: Texture) -> Unit) { + inline fun renderToTexture( + width: Int, height: Int, + render: (AG.RenderBuffer) -> Unit = {}, + hasDepth: Boolean = false, hasStencil: Boolean = false, msamples: Int = 1, + use: (texture: Texture) -> Unit + ) { flush() ag.renderToTexture(width, height, render = { val oldScissors = batch.scissor @@ -120,7 +129,7 @@ class RenderContext constructor( batch.scissor = oldScissors } } - }, use = { tex, texWidth, texHeight -> + }, hasDepth = hasDepth, hasStencil = hasStencil, msamples = msamples, use = { tex, texWidth, texHeight -> use(Texture(tex, texWidth, texHeight).slice(0, 0, width, height)) flush() }) @@ -129,17 +138,29 @@ class RenderContext constructor( /** * Sets the render buffer temporarily to [bmp] [Bitmap32] and calls the [callback] render method that should perform all the renderings inside. */ - inline fun renderToBitmap(bmp: Bitmap32, callback: () -> Unit): Bitmap32 { + inline fun renderToBitmap( + bmp: Bitmap32, + hasDepth: Boolean = false, hasStencil: Boolean = false, msamples: Int = 1, + callback: () -> Unit + ): Bitmap32 { flush() - ag.renderToBitmap(bmp) { + ag.renderToBitmap(bmp, hasDepth, hasStencil, msamples) { callback() flush() } return bmp } - inline fun renderToBitmap(width: Int, height: Int, callback: () -> Unit): Bitmap32 = - renderToBitmap(Bitmap32(width, height), callback) + inline fun renderToBitmap( + width: Int, height: Int, + hasDepth: Boolean = false, hasStencil: Boolean = false, msamples: Int = 1, + callback: () -> Unit + ): Bitmap32 = + renderToBitmap( + Bitmap32(width, height), + hasDepth = hasDepth, hasStencil = hasStencil, msamples = msamples, + callback = callback + ) /** * Finishes the drawing and flips the screen. Called by the KorGe engine at the end of the frame. diff --git a/korge/src/commonMain/kotlin/com/soywiz/korge/view/ViewRenderToBitmap.kt b/korge/src/commonMain/kotlin/com/soywiz/korge/view/ViewRenderToBitmap.kt index d643bb1fd..296513fbc 100644 --- a/korge/src/commonMain/kotlin/com/soywiz/korge/view/ViewRenderToBitmap.kt +++ b/korge/src/commonMain/kotlin/com/soywiz/korge/view/ViewRenderToBitmap.kt @@ -31,7 +31,7 @@ fun View.unsafeRenderToBitmapSync(ctx: RenderContext): Bitmap32 { return Bitmap32(bounds.width.toInt(), bounds.height.toInt()).also { bmp -> //val ctx = RenderContext(views.ag, coroutineContext = views.coroutineContext) //views.ag.renderToBitmap(bmp) { - ctx.renderToBitmap(bmp) { + ctx.renderToBitmap(bmp, hasStencil = true) { ctx.useBatcher { batch -> batch.setViewMatrixTemp(view.globalMatrixInv) { view.render(ctx) diff --git a/korge/src/jvmTest/kotlin/com/soywiz/korge/view/ViewsOpenglJvmTest.kt b/korge/src/jvmTest/kotlin/com/soywiz/korge/view/ViewsOpenglJvmTest.kt index 561678e28..e318eaf4c 100644 --- a/korge/src/jvmTest/kotlin/com/soywiz/korge/view/ViewsOpenglJvmTest.kt +++ b/korge/src/jvmTest/kotlin/com/soywiz/korge/view/ViewsOpenglJvmTest.kt @@ -2,10 +2,12 @@ package com.soywiz.korge.view import com.soywiz.kgl.* import com.soywiz.korag.* +import com.soywiz.korge.render.* import com.soywiz.korge.test.* import com.soywiz.korge.tests.* import com.soywiz.korge.view.filter.* import com.soywiz.korim.bitmap.* +import com.soywiz.korim.color.* import org.junit.* class ViewsOpenglJvmTest : ViewsForTesting(log = true) { @@ -31,4 +33,32 @@ class ViewsOpenglJvmTest : ViewsForTesting(log = true) { assertEqualsFileReference("korge/render/ViewsJvmTestOpenglFilterIdentityDefaultRenderBufferSize.log", logGl.getLogAsString()) } + @Test + fun testRenderToTextureWithStencil() { + views.stage += object : View() { + override fun renderInternal(ctx: RenderContext) { + ctx.renderToTexture(100, 100, render = { + ctx.useCtx2d { ctx2d -> + ctx2d.rect(0.0, 0.0, 100.0, 100.0, Colors.RED) + } + }, hasStencil = true) { tex -> + ctx.useBatcher { batcher -> + batcher.drawQuad(tex) + } + } + ctx.renderToTexture(100, 100, render = { + ctx.useCtx2d { ctx2d -> + ctx2d.rect(0.0, 0.0, 100.0, 100.0, Colors.BLUE) + } + }, hasDepth = true, hasStencil = true) { tex -> + ctx.useBatcher { batcher -> + batcher.drawQuad(tex, 100f, 0f) + } + } + } + } + views.render() + assertEqualsFileReference("korge/render/ViewsJvmTestOpenglRenderToTextureWithStencil.log", logGl.getLogAsString()) + } + } diff --git a/korge/src/jvmTest/resources/korge/render/ViewsJvmTestOpenglFilterIdentityDefaultRenderBufferSize.log b/korge/src/jvmTest/resources/korge/render/ViewsJvmTestOpenglFilterIdentityDefaultRenderBufferSize.log index 5f4b7c0b5..9cf8b57f8 100644 --- a/korge/src/jvmTest/resources/korge/render/ViewsJvmTestOpenglFilterIdentityDefaultRenderBufferSize.log +++ b/korge/src/jvmTest/resources/korge/render/ViewsJvmTestOpenglFilterIdentityDefaultRenderBufferSize.log @@ -15,10 +15,8 @@ texParameteri([3553, 10241, 9729]) texImage2D([3553, 0, 6408, 128, 128, 0, 6408, 5121, null]) bindTexture([3553, 0]) bindRenderbuffer([36161, 0]) -renderbufferStorage([36161, 33189, 128, 128]) bindFramebuffer([36160, 0]) framebufferTexture2D([36160, 36064, 3553, 0, 0]) -framebufferRenderbuffer([36160, 36096, 36161, 0]) disable([3089]) colorMask([true, true, true, true]) clearColor([0.0, 0.0, 0.0, 0.0]) diff --git a/korge/src/jvmTest/resources/korge/render/ViewsJvmTestOpenglRenderToTextureWithStencil.log b/korge/src/jvmTest/resources/korge/render/ViewsJvmTestOpenglRenderToTextureWithStencil.log new file mode 100644 index 000000000..544e73beb --- /dev/null +++ b/korge/src/jvmTest/resources/korge/render/ViewsJvmTestOpenglRenderToTextureWithStencil.log @@ -0,0 +1,318 @@ +colorMask([true, true, true, true]) +clearColor([0.0, 0.0, 0.0, 1.0]) +depthMask([true]) +clearDepthf([1.0]) +stencilMask([-1]) +clearStencil([0]) +clear([17664]) +viewport([0, 0, 128, 128]) +genRenderbuffers([1, FBuffer(size=4)]) +genFramebuffers([1, FBuffer(size=4)]) +genTextures([1, FBuffer(size=4)]) +bindTexture([3553, 0]) +texParameteri([3553, 10240, 9729]) +texParameteri([3553, 10241, 9729]) +texImage2D([3553, 0, 6408, 128, 128, 0, 6408, 5121, null]) +bindTexture([3553, 0]) +bindRenderbuffer([36161, 0]) +renderbufferStorage([36161, 36168, 128, 128]) +bindFramebuffer([36160, 0]) +framebufferTexture2D([36160, 36064, 3553, 0, 0]) +framebufferRenderbuffer([36160, 36128, 36161, 0]) +disable([3089]) +colorMask([true, true, true, true]) +clearColor([0.0, 0.0, 0.0, 0.0]) +depthMask([true]) +clearDepthf([1.0]) +stencilMask([-1]) +clearStencil([0]) +clear([17664]) +enable([3089]) +scissor([0, 0, 100, 100]) +genBuffers([1, FBuffer(size=4)]) +bindBuffer([34963, 0]) +bufferData([34963, 12, FBuffer(size=49152), 35044]) +bindBuffer([34963, 0]) +createProgram([]) = 0 +getString([35724]) = +createShader([35632]) = 0 +shaderSource([0, ...]) +compileShader([0]) +getShaderiv([0, 35713, FBuffer(size=4)]) +getError([]) = 0 +createShader([35633]) = 0 +shaderSource([0, ...]) +compileShader([0]) +getShaderiv([0, 35713, FBuffer(size=4)]) +getError([]) = 0 +attachShader([0, 0]) +attachShader([0, 0]) +linkProgram([0]) +getProgramiv([0, 35714, FBuffer(size=4)]) +useProgram([0]) +genBuffers([1, FBuffer(size=4)]) +bindBuffer([34962, 0]) +bufferData([34962, 96, FBuffer(size=393216), 35044]) +bindBuffer([34962, 0]) +getAttribLocation([0, a_Pos]) = 0 +enableVertexAttribArray([0]) +vertexAttribPointer([0, 2, 5126, false, 24, 0]) +getAttribLocation([0, a_Tex]) = 0 +enableVertexAttribArray([0]) +vertexAttribPointer([0, 2, 5126, false, 24, 8]) +getAttribLocation([0, a_Col]) = 0 +enableVertexAttribArray([0]) +vertexAttribPointer([0, 4, 5121, true, 24, 16]) +getAttribLocation([0, a_Col2]) = 0 +enableVertexAttribArray([0]) +vertexAttribPointer([0, 4, 5121, true, 24, 20]) +genBuffers([1, FBuffer(size=4)]) +bindBuffer([34962, 0]) +bufferData([34962, 4, FBuffer(size=4), 35044]) +bindBuffer([34962, 0]) +getAttribLocation([0, a_TexIndex]) = 0 +enableVertexAttribArray([0]) +vertexAttribPointer([0, 1, 5121, false, 1, 0]) +getUniformLocation([0, u_ProjMat]) = 0 +uniformMatrix4fv([0, 1, false, FBuffer(size=65536)]) +getUniformLocation([0, u_ViewMat]) = 0 +uniformMatrix4fv([0, 1, false, FBuffer(size=65536)]) +getUniformLocation([0, u_Tex0]) = 0 +activeTexture([33984]) +genTextures([1, FBuffer(size=4)]) +bindTexture([3553, 0]) +texImage2D([3553, 0, 6408, 1, 1, 0, 6408, 5121, FBuffer(size=4)]) +texParameteri([3553, 10242, 33071]) +texParameteri([3553, 10243, 33071]) +texParameteri([3553, 10241, 9729]) +texParameteri([3553, 10240, 9729]) +uniform1i([0, 0]) +getUniformLocation([0, u_Tex1]) = 0 +activeTexture([33985]) +uniform1i([0, 1]) +getUniformLocation([0, u_Tex2]) = 0 +activeTexture([33986]) +uniform1i([0, 2]) +getUniformLocation([0, u_Tex3]) = 0 +activeTexture([33987]) +uniform1i([0, 3]) +enable([3042]) +blendEquationSeparate([32774, 32774]) +blendFuncSeparate([770, 771, 1, 771]) +disable([2884]) +depthMask([true]) +depthRangef([0.0, 1.0]) +lineWidth([1.0]) +disable([2929]) +colorMask([true, true, true, true]) +disable([2960]) +stencilMask([0]) +drawElements([4, 6, 5123, 0]) +disableVertexAttribArray([0]) +disableVertexAttribArray([0]) +disableVertexAttribArray([0]) +disableVertexAttribArray([0]) +disableVertexAttribArray([0]) +bindBuffer([34963, 0]) +bufferData([34963, 12, FBuffer(size=49152), 35044]) +bindBuffer([34963, 0]) +createProgram([]) = 0 +createShader([35632]) = 0 +shaderSource([0, ...]) +compileShader([0]) +getShaderiv([0, 35713, FBuffer(size=4)]) +getError([]) = 0 +createShader([35633]) = 0 +shaderSource([0, ...]) +compileShader([0]) +getShaderiv([0, 35713, FBuffer(size=4)]) +getError([]) = 0 +attachShader([0, 0]) +attachShader([0, 0]) +linkProgram([0]) +getProgramiv([0, 35714, FBuffer(size=4)]) +useProgram([0]) +bindBuffer([34962, 0]) +bufferData([34962, 96, FBuffer(size=393216), 35044]) +bindBuffer([34962, 0]) +getAttribLocation([0, a_Pos]) = 0 +enableVertexAttribArray([0]) +vertexAttribPointer([0, 2, 5126, false, 24, 0]) +getAttribLocation([0, a_Tex]) = 0 +enableVertexAttribArray([0]) +vertexAttribPointer([0, 2, 5126, false, 24, 8]) +getAttribLocation([0, a_Col]) = 0 +enableVertexAttribArray([0]) +vertexAttribPointer([0, 4, 5121, true, 24, 16]) +getAttribLocation([0, a_Col2]) = 0 +enableVertexAttribArray([0]) +vertexAttribPointer([0, 4, 5121, true, 24, 20]) +bindBuffer([34962, 0]) +bufferData([34962, 4, FBuffer(size=4), 35044]) +bindBuffer([34962, 0]) +getAttribLocation([0, a_TexIndex]) = 0 +enableVertexAttribArray([0]) +vertexAttribPointer([0, 1, 5121, false, 1, 0]) +getUniformLocation([0, u_ProjMat]) = 0 +uniformMatrix4fv([0, 1, false, FBuffer(size=65536)]) +getUniformLocation([0, u_ViewMat]) = 0 +uniformMatrix4fv([0, 1, false, FBuffer(size=65536)]) +getUniformLocation([0, u_Tex0]) = 0 +activeTexture([33984]) +bindTexture([3553, 0]) +texParameteri([3553, 10242, 33071]) +texParameteri([3553, 10243, 33071]) +texParameteri([3553, 10241, 9729]) +texParameteri([3553, 10240, 9729]) +uniform1i([0, 0]) +getUniformLocation([0, u_Tex1]) = 0 +activeTexture([33985]) +uniform1i([0, 1]) +getUniformLocation([0, u_Tex2]) = 0 +activeTexture([33986]) +uniform1i([0, 2]) +getUniformLocation([0, u_Tex3]) = 0 +activeTexture([33987]) +uniform1i([0, 3]) +enable([3042]) +blendEquationSeparate([32774, 32774]) +blendFuncSeparate([770, 771, 1, 771]) +disable([2884]) +depthMask([true]) +depthRangef([0.0, 1.0]) +lineWidth([1.0]) +disable([2929]) +colorMask([true, true, true, true]) +disable([2960]) +stencilMask([0]) +drawElements([4, 6, 5123, 0]) +disableVertexAttribArray([0]) +disableVertexAttribArray([0]) +disableVertexAttribArray([0]) +disableVertexAttribArray([0]) +disableVertexAttribArray([0]) +viewport([0, 0, 128, 128]) +bindTexture([3553, 0]) +texParameteri([3553, 10240, 9729]) +texParameteri([3553, 10241, 9729]) +texImage2D([3553, 0, 6408, 128, 128, 0, 6408, 5121, null]) +bindTexture([3553, 0]) +bindRenderbuffer([36161, 0]) +renderbufferStorage([36161, 34041, 128, 128]) +bindFramebuffer([36160, 0]) +framebufferTexture2D([36160, 36064, 3553, 0, 0]) +framebufferRenderbuffer([36160, 33306, 36161, 0]) +disable([3089]) +colorMask([true, true, true, true]) +clearColor([0.0, 0.0, 0.0, 0.0]) +depthMask([true]) +clearDepthf([1.0]) +stencilMask([-1]) +clearStencil([0]) +clear([17664]) +enable([3089]) +scissor([0, 0, 100, 100]) +bindBuffer([34963, 0]) +bufferData([34963, 12, FBuffer(size=49152), 35044]) +bindBuffer([34963, 0]) +useProgram([0]) +bindBuffer([34962, 0]) +bufferData([34962, 96, FBuffer(size=393216), 35044]) +bindBuffer([34962, 0]) +enableVertexAttribArray([0]) +vertexAttribPointer([0, 2, 5126, false, 24, 0]) +enableVertexAttribArray([0]) +vertexAttribPointer([0, 2, 5126, false, 24, 8]) +enableVertexAttribArray([0]) +vertexAttribPointer([0, 4, 5121, true, 24, 16]) +enableVertexAttribArray([0]) +vertexAttribPointer([0, 4, 5121, true, 24, 20]) +bindBuffer([34962, 0]) +bufferData([34962, 4, FBuffer(size=4), 35044]) +bindBuffer([34962, 0]) +enableVertexAttribArray([0]) +vertexAttribPointer([0, 1, 5121, false, 1, 0]) +uniformMatrix4fv([0, 1, false, FBuffer(size=65536)]) +uniformMatrix4fv([0, 1, false, FBuffer(size=65536)]) +activeTexture([33984]) +bindTexture([3553, 0]) +texParameteri([3553, 10242, 33071]) +texParameteri([3553, 10243, 33071]) +texParameteri([3553, 10241, 9729]) +texParameteri([3553, 10240, 9729]) +uniform1i([0, 0]) +activeTexture([33985]) +uniform1i([0, 1]) +activeTexture([33986]) +uniform1i([0, 2]) +activeTexture([33987]) +uniform1i([0, 3]) +enable([3042]) +blendEquationSeparate([32774, 32774]) +blendFuncSeparate([770, 771, 1, 771]) +disable([2884]) +depthMask([true]) +depthRangef([0.0, 1.0]) +lineWidth([1.0]) +disable([2929]) +colorMask([true, true, true, true]) +disable([2960]) +stencilMask([0]) +drawElements([4, 6, 5123, 0]) +disableVertexAttribArray([0]) +disableVertexAttribArray([0]) +disableVertexAttribArray([0]) +disableVertexAttribArray([0]) +disableVertexAttribArray([0]) +bindBuffer([34963, 0]) +bufferData([34963, 12, FBuffer(size=49152), 35044]) +bindBuffer([34963, 0]) +useProgram([0]) +bindBuffer([34962, 0]) +bufferData([34962, 96, FBuffer(size=393216), 35044]) +bindBuffer([34962, 0]) +enableVertexAttribArray([0]) +vertexAttribPointer([0, 2, 5126, false, 24, 0]) +enableVertexAttribArray([0]) +vertexAttribPointer([0, 2, 5126, false, 24, 8]) +enableVertexAttribArray([0]) +vertexAttribPointer([0, 4, 5121, true, 24, 16]) +enableVertexAttribArray([0]) +vertexAttribPointer([0, 4, 5121, true, 24, 20]) +bindBuffer([34962, 0]) +bufferData([34962, 4, FBuffer(size=4), 35044]) +bindBuffer([34962, 0]) +enableVertexAttribArray([0]) +vertexAttribPointer([0, 1, 5121, false, 1, 0]) +uniformMatrix4fv([0, 1, false, FBuffer(size=65536)]) +uniformMatrix4fv([0, 1, false, FBuffer(size=65536)]) +activeTexture([33984]) +bindTexture([3553, 0]) +texParameteri([3553, 10242, 33071]) +texParameteri([3553, 10243, 33071]) +texParameteri([3553, 10241, 9729]) +texParameteri([3553, 10240, 9729]) +uniform1i([0, 0]) +activeTexture([33985]) +uniform1i([0, 1]) +activeTexture([33986]) +uniform1i([0, 2]) +activeTexture([33987]) +uniform1i([0, 3]) +enable([3042]) +blendEquationSeparate([32774, 32774]) +blendFuncSeparate([770, 771, 1, 771]) +disable([2884]) +depthMask([true]) +depthRangef([0.0, 1.0]) +lineWidth([1.0]) +disable([2929]) +colorMask([true, true, true, true]) +disable([2960]) +stencilMask([0]) +drawElements([4, 6, 5123, 0]) +disableVertexAttribArray([0]) +disableVertexAttribArray([0]) +disableVertexAttribArray([0]) +disableVertexAttribArray([0]) +disableVertexAttribArray([0]) \ No newline at end of file diff --git a/korgw/src/commonMain/kotlin/com/soywiz/kgl/IKmlGl.kt b/korgw/src/commonMain/kotlin/com/soywiz/kgl/IKmlGl.kt index dcd3ff73b..aaf2b8e96 100644 --- a/korgw/src/commonMain/kotlin/com/soywiz/kgl/IKmlGl.kt +++ b/korgw/src/commonMain/kotlin/com/soywiz/kgl/IKmlGl.kt @@ -160,5 +160,6 @@ interface IKmlGl { fun drawArraysInstanced(mode: Int, first: Int, count: Int, instancecount: Int): Unit = unsupported("Not supported instanced drawing") fun drawElementsInstanced(mode: Int, count: Int, type: Int, indices: Int, instancecount: Int): Unit = unsupported("Not supported instanced drawing") fun vertexAttribDivisor(index: Int, divisor: Int): Unit = unsupported() + fun renderbufferStorageMultisample(target: Int, samples: Int, internalformat: Int, width: Int, height: Int): Unit = unsupported("Not supported MSAA") } diff --git a/korgw/src/commonMain/kotlin/com/soywiz/kgl/KmlGl.kt b/korgw/src/commonMain/kotlin/com/soywiz/kgl/KmlGl.kt index 0c3ec9899..af6ca87b8 100644 --- a/korgw/src/commonMain/kotlin/com/soywiz/kgl/KmlGl.kt +++ b/korgw/src/commonMain/kotlin/com/soywiz/kgl/KmlGl.kt @@ -298,6 +298,7 @@ abstract class KmlGl : Extra by Extra.Mixin(), IKmlGl, AGFeatures { val RGB5_A1: Int = 0x8057 val RGB565: Int = 0x8D62 val DEPTH_COMPONENT16: Int = 0x81A5 + val STENCIL_INDEX: Int = 0x1901 val STENCIL_INDEX8: Int = 0x8D48 val RENDERBUFFER_WIDTH: Int = 0x8D42 val RENDERBUFFER_HEIGHT: Int = 0x8D43 @@ -314,6 +315,8 @@ abstract class KmlGl : Extra by Extra.Mixin(), IKmlGl, AGFeatures { val FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE: Int = 0x8CD3 val COLOR_ATTACHMENT0: Int = 0x8CE0 val DEPTH_ATTACHMENT: Int = 0x8D00 + val DEPTH_STENCIL_ATTACHMENT = 0x821A + val DEPTH_STENCIL = 0x84F9 val STENCIL_ATTACHMENT: Int = 0x8D20 val NONE: Int = 0x0000 val FRAMEBUFFER_COMPLETE: Int = 0x8CD5 diff --git a/korgw/src/commonMain/kotlin/com/soywiz/kgl/KmlGlProxy.kt b/korgw/src/commonMain/kotlin/com/soywiz/kgl/KmlGlProxy.kt index 77a88c2cb..bfde695a5 100644 --- a/korgw/src/commonMain/kotlin/com/soywiz/kgl/KmlGlProxy.kt +++ b/korgw/src/commonMain/kotlin/com/soywiz/kgl/KmlGlProxy.kt @@ -1044,11 +1044,22 @@ open class KmlGlFastProxy(var parent: KmlGl) : KmlGl() { // Instanced override val isInstancedSupported: Boolean get() = parent.isInstancedSupported + override val isStorageMultisampleSupported: Boolean get() = parent.isStorageMultisampleSupported override fun drawArraysInstanced(mode: Int, first: Int, count: Int, instancecount: Int) = parent.drawArraysInstanced(mode, first, count, instancecount) override fun drawElementsInstanced(mode: Int, count: Int, type: Int, indices: Int, instancecount: Int) = parent.drawElementsInstanced(mode, count, type, indices, instancecount) override fun vertexAttribDivisor(index: Int, divisor: Int) = parent.vertexAttribDivisor(index, divisor) + override fun renderbufferStorageMultisample( + target: Int, + samples: Int, + internalformat: Int, + width: Int, + height: Int + ) { + parent.renderbufferStorageMultisample(target, samples, internalformat, width, height) + } + override var info: ContextInfo get() = parent.info set(value) { parent.info = value } diff --git a/korgw/src/commonMain/kotlin/com/soywiz/korag/AG.kt b/korgw/src/commonMain/kotlin/com/soywiz/korag/AG.kt index 5a847b0de..a8a13688e 100644 --- a/korgw/src/commonMain/kotlin/com/soywiz/korag/AG.kt +++ b/korgw/src/commonMain/kotlin/com/soywiz/korag/AG.kt @@ -48,6 +48,7 @@ interface AGWindow : AGContainer { interface AGFeatures { val graphicExtensions: Set get() = emptySet() val isInstancedSupported: Boolean get() = false + val isStorageMultisampleSupported: Boolean get() = false val isFloatTextureSupported: Boolean get() = false } @@ -150,11 +151,12 @@ abstract class AG : AGFeatures, Extra by Extra.Mixin() { fun copyFrom(that: Scissor): Scissor = setTo(that.x, that.y, that.width, that.height) - fun setTo(x: Double, y: Double, width: Double, height: Double): Scissor = this.apply { + fun setTo(x: Double, y: Double, width: Double, height: Double): Scissor { this.x = x this.y = y this.width = width this.height = height + return this } fun setTo(x: Int, y: Int, width: Int, height: Int): Scissor = @@ -582,6 +584,7 @@ abstract class AG : AGFeatures, Extra by Extra.Mixin() { //open val supportInstancedDrawing: Boolean get() = false + @Deprecated("Use draw(Batch) or drawV2() instead") fun draw( vertices: Buffer, program: Program, @@ -762,6 +765,9 @@ abstract class AG : AGFeatures, Extra by Extra.Mixin() { open val id: Int = -1 private var cachedTexVersion = -1 private var _tex: Texture? = null + protected var nsamples: Int = 1 + protected var hasDepth: Boolean = true + protected var hasStencil: Boolean = true val tex: AG.Texture get() { @@ -785,6 +791,21 @@ abstract class AG : AGFeatures, Extra by Extra.Mixin() { } } + fun setSamples(samples: Int) { + if (this.nsamples != samples) { + nsamples = samples + dirty = true + } + } + + fun setExtra(hasDepth: Boolean = true, hasStencil: Boolean = true) { + if (this.hasDepth != hasDepth || this.hasStencil != hasStencil) { + this.hasDepth = hasDepth + this.hasStencil = hasStencil + dirty = true + } + } + override fun set(): Unit = Unit fun readBitmap(bmp: Bitmap32) = this@AG.readColor(bmp) fun readDepth(width: Int, height: Int, out: FloatArray): Unit = this@AG.readDepth(width, height, out) @@ -811,14 +832,21 @@ abstract class AG : AGFeatures, Extra by Extra.Mixin() { stencil: Int = 0, clearColor: Boolean = true, clearDepth: Boolean = true, - clearStencil: Boolean = true + clearStencil: Boolean = true, + scissor: AG.Scissor? = null ) = Unit + fun clearStencil(stencil: Int = 0, scissor: AG.Scissor? = null) = clear(clearColor = false, clearDepth = false, clearStencil = true, stencil = stencil, scissor = scissor) + fun clearDepth(depth: Float = 1f, scissor: AG.Scissor? = null) = clear(clearColor = false, clearDepth = true, clearStencil = false, depth = depth, scissor = scissor) + fun clearColor(color: RGBA = Colors.TRANSPARENT_BLACK, scissor: AG.Scissor? = null) = clear(clearColor = true, clearDepth = false, clearStencil = false, color = color, scissor = scissor) + //@PublishedApi @KoragExperimental var currentRenderBuffer: BaseRenderBuffer? = null private set + val currentRenderBufferOrMain: BaseRenderBuffer get() = currentRenderBuffer ?: mainRenderBuffer + val renderingToTexture get() = currentRenderBuffer !== mainRenderBuffer && currentRenderBuffer !== null inline fun backupTexture(tex: Texture?, callback: () -> Unit) { @@ -848,12 +876,14 @@ abstract class AG : AGFeatures, Extra by Extra.Mixin() { //open fun fixHeightForRenderToTexture(height: Int): Int = height @KoragExperimental - fun unsafeAllocateFrameRenderBuffer(width: Int, height: Int): RenderBuffer { + fun unsafeAllocateFrameRenderBuffer(width: Int, height: Int, hasDepth: Boolean = false, hasStencil: Boolean = false, msamples: Int = 1): RenderBuffer { val realWidth = fixWidthForRenderToTexture(width) val realHeight = fixHeightForRenderToTexture(height) val rb = renderBuffers.alloc() frameRenderBuffers += rb rb.setSize(0, 0, realWidth, realHeight, realWidth, realHeight) + rb.setExtra(hasDepth = hasDepth, hasStencil = hasStencil) + rb.setSamples(msamples) //println("unsafeAllocateFrameRenderBuffer($width, $height), real($realWidth, $realHeight), $rb") return rb } @@ -865,8 +895,13 @@ abstract class AG : AGFeatures, Extra by Extra.Mixin() { } @OptIn(KoragExperimental::class) - inline fun renderToTexture(width: Int, height: Int, render: (rb: RenderBuffer) -> Unit, use: (tex: Texture, texWidth: Int, texHeight: Int) -> Unit) { - val rb = unsafeAllocateFrameRenderBuffer(width, height) + inline fun renderToTexture( + width: Int, height: Int, + render: (rb: RenderBuffer) -> Unit, + hasDepth: Boolean = false, hasStencil: Boolean = false, msamples: Int = 1, + use: (tex: Texture, texWidth: Int, texHeight: Int) -> Unit + ) { + val rb = unsafeAllocateFrameRenderBuffer(width, height, hasDepth, hasStencil, msamples) try { setRenderBufferTemporally(rb) { clear(Colors.TRANSPARENT_BLACK) // transparent @@ -878,12 +913,16 @@ abstract class AG : AGFeatures, Extra by Extra.Mixin() { } } - inline fun renderToBitmap(bmp: Bitmap32, render: () -> Unit) { + inline fun renderToBitmap( + bmp: Bitmap32, + hasDepth: Boolean = false, hasStencil: Boolean = false, msamples: Int = 1, + render: () -> Unit + ) { renderToTexture(bmp.width, bmp.height, render = { render() //println("renderToBitmap.readColor: $currentRenderBuffer") readColor(bmp) - }, { _, _, _ -> }) + }, hasDepth = hasDepth, hasStencil = hasStencil, msamples = msamples, use = { _, _, _ -> }) } fun setRenderBuffer(renderBuffer: BaseRenderBuffer?): BaseRenderBuffer? { @@ -896,6 +935,7 @@ abstract class AG : AGFeatures, Extra by Extra.Mixin() { open fun readColor(bitmap: Bitmap32): Unit = TODO() open fun readDepth(width: Int, height: Int, out: FloatArray): Unit = TODO() + open fun readStencil(bitmap: Bitmap8): Unit = TODO() fun readDepth(out: FloatArray2): Unit = readDepth(out.width, out.height, out.data) open fun readColorTexture(texture: Texture, width: Int = backWidth, height: Int = backHeight): Unit = TODO() fun readColor() = Bitmap32(backWidth, backHeight).apply { readColor(this) } @@ -907,14 +947,14 @@ abstract class AG : AGFeatures, Extra by Extra.Mixin() { val vertexLayout = VertexLayout(DefaultShaders.a_Pos, DefaultShaders.a_Tex) val verticesData = FBuffer(VERTEX_COUNT * vertexLayout.totalSize) val program = Program(VertexShader { - DefaultShaders.apply { - v_Tex setTo a_Tex - out setTo vec4(a_Pos, 0f.lit, 1f.lit) + DefaultShaders { + SET(v_Tex, a_Tex) + SET(out, vec4(a_Pos, 0f.lit, 1f.lit)) } }, FragmentShader { - DefaultShaders.apply { + DefaultShaders { //out setTo vec4(1f, 1f, 0f, 1f) - out setTo texture2D(u_Tex, v_Tex["xy"]) + SET(out, texture2D(u_Tex, v_Tex["xy"])) } }) val uniforms = UniformValues() diff --git a/korgw/src/commonMain/kotlin/com/soywiz/korag/DefaultShaders.kt b/korgw/src/commonMain/kotlin/com/soywiz/korag/DefaultShaders.kt index 92723a631..6ac946f96 100644 --- a/korgw/src/commonMain/kotlin/com/soywiz/korag/DefaultShaders.kt +++ b/korgw/src/commonMain/kotlin/com/soywiz/korag/DefaultShaders.kt @@ -9,6 +9,21 @@ fun ProgramWithDefault( name: String = "program" ): Program = Program(vertex, fragment, name) +interface IDefaultShaders { + val u_Tex: Uniform get() = DefaultShaders.u_Tex + val u_ProjMat: Uniform get() = DefaultShaders.u_ProjMat + val u_ViewMat: Uniform get() = DefaultShaders.u_ViewMat + val a_Pos: Attribute get() = DefaultShaders.a_Pos + val a_Tex: Attribute get() = DefaultShaders.a_Tex + val a_Col: Attribute get() = DefaultShaders.a_Col + val v_Tex: Varying get() = DefaultShaders.v_Tex + val v_Col: Varying get() = DefaultShaders.v_Col + val t_Temp0: Temp get() = DefaultShaders.t_Temp0 + val t_Temp1: Temp get() = DefaultShaders.t_Temp1 + val t_TempMat2: Temp get() = DefaultShaders.t_TempMat2 + val textureUnit: AG.TextureUnit get() = DefaultShaders.textureUnit +} + object DefaultShaders { // from korge val u_Tex: Uniform = Uniform("u_Tex", VarType.TextureUnit) @@ -101,3 +116,11 @@ object DefaultShaders { inline operator fun invoke(callback: DefaultShaders.() -> Unit): DefaultShaders = this.apply(callback) } + +class ProgramBuilderDefault : Program.Builder(), IDefaultShaders + +inline fun VertexShaderDefault(callback: ProgramBuilderDefault.() -> Unit): VertexShader = + VertexShader(ProgramBuilderDefault().also(callback)._buildFuncs()) + +inline fun FragmentShaderDefault(callback: ProgramBuilderDefault.() -> Unit): FragmentShader = + FragmentShader(ProgramBuilderDefault().also(callback)._buildFuncs()) diff --git a/korgw/src/commonMain/kotlin/com/soywiz/korag/OpenglAG.kt b/korgw/src/commonMain/kotlin/com/soywiz/korag/OpenglAG.kt index 6e1fc3456..4697e821b 100644 --- a/korgw/src/commonMain/kotlin/com/soywiz/korag/OpenglAG.kt +++ b/korgw/src/commonMain/kotlin/com/soywiz/korag/OpenglAG.kt @@ -120,12 +120,34 @@ abstract class AGOpengl : AG() { gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null) gl.bindTexture(gl.TEXTURE_2D, 0) gl.bindRenderbuffer(gl.RENDERBUFFER, depth.getInt(0)) - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, width, height) + val internalFormat = when { + hasStencil && hasDepth -> gl.DEPTH_STENCIL + hasStencil -> gl.STENCIL_INDEX8 + hasDepth -> gl.DEPTH_COMPONENT + else -> 0 + } + if (internalFormat != 0) { + if (nsamples != 1) { + //gl.renderbufferStorageMultisample(gl.RENDERBUFFER, nsamples, internalFormat, width, height) + gl.renderbufferStorage(gl.RENDERBUFFER, internalFormat, width, height) + } else { + gl.renderbufferStorage(gl.RENDERBUFFER, internalFormat, width, height) + } + } + //gl.renderbufferStorageMultisample() } gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer.getInt(0)) gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, ftex.tex, 0) - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depth.getInt(0)) + val internalFormat = when { + hasStencil && hasDepth -> gl.DEPTH_STENCIL_ATTACHMENT + hasStencil -> gl.STENCIL_ATTACHMENT + hasDepth -> gl.DEPTH_ATTACHMENT + else -> 0 + } + if (internalFormat != 0) { + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, internalFormat, gl.RENDERBUFFER, depth.getInt(0)) + } } override fun close() { @@ -563,11 +585,11 @@ abstract class AGOpengl : AG() { IndexType.UINT -> gl.UNSIGNED_INT } - private val programs = HashMap>() + private val programs = FastIdentityMap>() @JvmOverloads fun getProgram(program: Program, config: ProgramConfig = ProgramConfig.DEFAULT): GlProgram { - return programs.getOrPut(program) { HashMap() }.getOrPut(config) { GlProgram(gl, program, config) } + return programs.getOrPut(program) { FastIdentityMap() }.getOrPut(config) { GlProgram(gl, program, config) } } inner class GlProgram(val gl: KmlGl, val program: Program, val programConfig: ProgramConfig) : Closeable { @@ -680,11 +702,12 @@ abstract class AGOpengl : AG() { stencil: Int, clearColor: Boolean, clearDepth: Boolean, - clearStencil: Boolean + clearStencil: Boolean, + scissor: AG.Scissor?, ) { //println("CLEAR: $color, $depth") var bits = 0 - applyScissorState(null) + applyScissorState(scissor) //gl.disable(gl.SCISSOR_TEST) if (clearColor) { bits = bits or gl.COLOR_BUFFER_BIT @@ -1007,6 +1030,20 @@ abstract class AGOpengl : AG() { } } + override fun readStencil(bitmap: Bitmap8) { + fbuffer(bitmap.area * 1) { buffer -> + gl.readPixels( + 0, 0, bitmap.width, bitmap.height, + gl.STENCIL_INDEX, gl.UNSIGNED_BYTE, buffer + ) + buffer.getArrayInt8(0, bitmap.data, 0, bitmap.area) + //println("readColor.HASH:" + bitmap.computeHash()) + } + } + + + + override fun readColorTexture(texture: Texture, width: Int, height: Int) { gl.apply { texture.bind() diff --git a/korgw/src/commonMain/kotlin/com/soywiz/korag/log/LogAG.kt b/korgw/src/commonMain/kotlin/com/soywiz/korag/log/LogAG.kt index feaa112aa..981230270 100644 --- a/korgw/src/commonMain/kotlin/com/soywiz/korag/log/LogAG.kt +++ b/korgw/src/commonMain/kotlin/com/soywiz/korag/log/LogAG.kt @@ -163,8 +163,11 @@ open class LogBaseAG( stencil: Int, clearColor: Boolean, clearDepth: Boolean, - clearStencil: Boolean - ) = log("clear($color, $depth, $stencil, $clearColor, $clearDepth, $clearStencil)", Kind.CLEAR) + clearStencil: Boolean, + scissor: AG.Scissor?, + ) { + log("clear($color, $depth, $stencil, $clearColor, $clearDepth, $clearStencil)", Kind.CLEAR) + } override var backWidth: Int = width; set(value) { field = value; log("backWidth = $value", Kind.METRICS) } override var backHeight: Int = height; set(value) { field = value; log("backHeight = $value", Kind.METRICS) } diff --git a/korgw/src/jsMain/kotlin/com/soywiz/kgl/KmlGlJsCanvas.kt b/korgw/src/jsMain/kotlin/com/soywiz/kgl/KmlGlJsCanvas.kt index a962e1c35..9e615f1dc 100644 --- a/korgw/src/jsMain/kotlin/com/soywiz/kgl/KmlGlJsCanvas.kt +++ b/korgw/src/jsMain/kotlin/com/soywiz/kgl/KmlGlJsCanvas.kt @@ -243,6 +243,14 @@ class KmlGlJsCanvas(val canvas: HTMLCanvasElement, val glOpts: dynamic) : KmlGlW override val isInstancedSupported: Boolean get() = (webglVersion >= 2) || (instancedArrays != null) + override fun renderbufferStorageMultisample(target: Int, samples: Int, internalformat: Int, width: Int, height: Int) { + if (webglVersion >= 2) { + gl.asDynamic().renderbufferStorageMultisample(target, samples, internalformat, width, height) + } else { + TODO() + } + } + override fun drawArraysInstanced(mode: Int, first: Int, count: Int, instancecount: Int) { if (webglVersion >= 2) { gl.asDynamic().drawArraysInstanced(mode, first, count, instancecount) diff --git a/korgw/src/jvmMain/kotlin/com/soywiz/korgw/awt/AwtGameWindow.kt b/korgw/src/jvmMain/kotlin/com/soywiz/korgw/awt/AwtGameWindow.kt index 2311dc215..ccf548aac 100644 --- a/korgw/src/jvmMain/kotlin/com/soywiz/korgw/awt/AwtGameWindow.kt +++ b/korgw/src/jvmMain/kotlin/com/soywiz/korgw/awt/AwtGameWindow.kt @@ -1,25 +1,28 @@ package com.soywiz.korgw.awt import com.soywiz.korev.* -import com.soywiz.korgw.internal.MicroDynamic +import com.soywiz.korgw.internal.* +import com.soywiz.korgw.internal.MicroDynamic.invoke +import com.soywiz.korgw.osx.* import com.soywiz.korgw.platform.* -import com.soywiz.korim.awt.toAwt -import com.soywiz.korim.bitmap.Bitmap -import com.soywiz.korim.color.* +import com.soywiz.korim.awt.* +import com.soywiz.korim.bitmap.* +import com.soywiz.korio.dynamic.* import com.soywiz.korio.file.std.* -import com.soywiz.korio.util.OS +import com.soywiz.korio.util.* import com.soywiz.korma.geom.* -import com.soywiz.korma.geom.Rectangle +import sun.awt.* +import sun.lwawt.* +import sun.lwawt.macosx.* import java.awt.* -import java.awt.Toolkit.getDefaultToolkit import java.awt.datatransfer.* import java.awt.dnd.* import java.awt.event.* +import java.awt.peer.* import java.io.* import javax.imageio.* import javax.swing.* - class AwtGameWindow(checkGl: Boolean, logGl: Boolean) : BaseAwtGameWindow() { override val ag: AwtAg = AwtAg(this, checkGl, logGl) @@ -40,7 +43,18 @@ class AwtGameWindow(checkGl: Boolean, logGl: Boolean) : BaseAwtGameWindow() { override fun ensureContext() { if (ctx == null) { - ctx = glContextFromComponent(frame) + if (OS.isMac) { + try { + //ctx = ProxiedMacAWTOpenglContext(frame) + ctx = glContextFromComponent(frame) + + } catch (e: Throwable) { + e.printStackTrace() + ctx = glContextFromComponent(frame) + } + } else { + ctx = glContextFromComponent(frame) + } } } diff --git a/korgw/src/jvmMain/kotlin/com/soywiz/korgw/awt/BaseAwtGameWindow.kt b/korgw/src/jvmMain/kotlin/com/soywiz/korgw/awt/BaseAwtGameWindow.kt index 2fe5bc996..316806da3 100644 --- a/korgw/src/jvmMain/kotlin/com/soywiz/korgw/awt/BaseAwtGameWindow.kt +++ b/korgw/src/jvmMain/kotlin/com/soywiz/korgw/awt/BaseAwtGameWindow.kt @@ -137,9 +137,6 @@ abstract class BaseAwtGameWindow : GameWindow() { ensureContext() - //GL.glClearColor(1f, 0f, 0f, 1f) - //GL.glClear(GL.GL_COLOR_BUFFER_BIT) - ctx?.useContext(g, ag, paintInContextDelegate) //Toolkit.getDefaultToolkit().sync(); } diff --git a/korgw/src/jvmMain/kotlin/com/soywiz/korgw/osx/Cocoa.kt b/korgw/src/jvmMain/kotlin/com/soywiz/korgw/osx/Cocoa.kt index a48438424..138b64ad2 100644 --- a/korgw/src/jvmMain/kotlin/com/soywiz/korgw/osx/Cocoa.kt +++ b/korgw/src/jvmMain/kotlin/com/soywiz/korgw/osx/Cocoa.kt @@ -16,7 +16,7 @@ annotation class NativeName(val name: String) { typealias NSRectPtr = Pointer -inline fun NativeLoad(name: String) = Native.load(name, T::class.java, NativeName.OPTIONS) as T +inline fun NativeLoad(name: String): T = Native.load(name, T::class.java, NativeName.OPTIONS) as T internal interface GL : Library { fun glViewport(x: Int, y: Int, width: Int, height: Int) @@ -49,7 +49,11 @@ interface ObjectiveC : Library { fun objc_msgSendCGFloat(vararg args: Any?): CGFloat @NativeName("objc_msgSend") fun objc_msgSendNSPoint(vararg args: Any?): NSPointRes + @NativeName("objc_msgSend") + fun objc_msgSendNSRect(vararg args: Any?): NSRectRes + @NativeName("objc_msgSend_stret") fun objc_msgSend_stret(structPtr: Any?, vararg args: Any?): Unit + /* fun objc_msgSend(a: Long, b: Long): Long fun objc_msgSend(a: Long, b: Long, c: Long): Long @@ -81,7 +85,7 @@ interface ObjectiveC : Library { fun property_getAttributes(prop: ID): String companion object : ObjectiveC by NativeLoad("objc") { - val NATIVE = NativeLibrary.getInstance("objc") + //val NATIVE = NativeLibrary.getInstance("objc") } } @@ -141,17 +145,60 @@ fun Foundation.NSLog(msg: String) = NSLog(NSString(msg)) //typealias NSPointRes = Long typealias NSPointRes = MyNativeNSPoint.ByValue +typealias NSRectRes = MyNativeNSRect.ByValue + +private val isArm64 = System.getProperty("os.arch") == "aarch64" fun sel(name: String) = ObjectiveC.sel_registerName(name) fun Long.msgSend(sel: String, vararg args: Any?): Long = ObjectiveC.objc_msgSend(this, sel(sel), *args) fun Long.msgSendInt(sel: String, vararg args: Any?): Int = ObjectiveC.objc_msgSendInt(this, sel(sel), *args) fun Long.msgSendCGFloat(sel: String, vararg args: Any?): CGFloat = ObjectiveC.objc_msgSendCGFloat(this, sel(sel), *args) fun Long.msgSendNSPoint(sel: String, vararg args: Any?): NSPointRes = ObjectiveC.objc_msgSendNSPoint(this, sel(sel), *args) -fun Long.msgSend_stret(output: Any?, sel: String, vararg args: Any?): Unit = ObjectiveC.objc_msgSend_stret(output, this, sel(sel), *args) +fun Long.msgSendNSRect(sel: String, vararg args: Any?): NSRectRes { + if (isArm64) { + return ObjectiveC.objc_msgSendNSRect(this, sel(sel), *args) + } else { + val rect = MyNSRect() + val out = NSRectRes() + this.msgSend_stret(rect, sel, *args) + out.x = rect.x + out.y = rect.y + out.width = rect.width + out.height = rect.height + return out + } +} +fun Long.msgSend_stret(output: Any?, sel: String, vararg args: Any?): Unit { + if (isArm64) error("Not available on arm64") + ObjectiveC.objc_msgSend_stret(output, this, sel(sel), *args) +} + +/* +open class NSRECT : Structure { + var x: Double = 0.0 + var y: Double = 0.0 + var width: Double = 0.0 + var height: Double = 0.0 + + constructor() : super() {} + constructor(peer: Pointer?) : super(peer) {} + + override fun getFieldOrder() = listOf("x", "y", "width", "height") + + class ByReference : NSRECT(), Structure.ByReference + class ByValue : NSRECT(), Structure.ByValue +} + */ + operator fun Long.invoke(sel: String, vararg args: Any?): Long = ObjectiveC.objc_msgSend(this, sel(sel), *args) open class NSObject(val id: Long) : IntegerType(8, id, false), NativeMapped { fun msgSend(sel: String, vararg args: Any?): Long = ObjectiveC.objc_msgSend(id, sel(sel), *args) + fun msgSendInt(sel: String, vararg args: Any?): Int = ObjectiveC.objc_msgSendInt(id, sel(sel), *args) + fun msgSendCGFloat(sel: String, vararg args: Any?): CGFloat = ObjectiveC.objc_msgSendCGFloat(id, sel(sel), *args) + fun msgSendNSPoint(sel: String, vararg args: Any?): NSPointRes = ObjectiveC.objc_msgSendNSPoint(id, sel(sel), *args) + fun msgSend_stret(sel: String, vararg args: Any?): Unit = ObjectiveC.objc_msgSend_stret(id, sel(sel), *args) + fun alloc(): Long = msgSend("alloc") companion object : NSClass("NSObject") { diff --git a/korgw/src/jvmMain/kotlin/com/soywiz/korgw/osx/MacosGLContext.kt b/korgw/src/jvmMain/kotlin/com/soywiz/korgw/osx/MacosGLContext.kt new file mode 100644 index 000000000..3a8ff554b --- /dev/null +++ b/korgw/src/jvmMain/kotlin/com/soywiz/korgw/osx/MacosGLContext.kt @@ -0,0 +1,271 @@ +package com.soywiz.korgw.osx + +import com.soywiz.kgl.* +import com.soywiz.korag.* +import com.soywiz.korgw.* +import com.soywiz.korgw.platform.* +import com.soywiz.korio.dynamic.* +import com.soywiz.korma.geom.* +import com.sun.jna.* +import java.awt.* +import java.security.* +import java.util.* +import javax.swing.SwingUtilities + +class MacosGLContext( + var contentView: Long = 0L, + val window: Long = 0L, + val quality: GameWindow.Quality = GameWindow.Quality.AUTOMATIC, + val sharedContext: Long = 0L, +) : BaseOpenglContext { + companion object { + const val NSOpenGLPFAMultisample = 59 + const val NSOpenGLPFASampleBuffers = 55 + const val NSOpenGLPFASamples = 56 + const val NSOpenGLPFADoubleBuffer = 5 + const val NSOpenGLPFAColorSize = 8 + const val NSOpenGLPFAAlphaSize = 11 + const val NSOpenGLPFADepthSize = 12 + const val NSOpenGLPFAStencilSize = 13 + const val NSOpenGLPFAAccumSize = 14 + } + + val attrs: IntArray by lazy { + val antialias = (this.quality != GameWindow.Quality.PERFORMANCE) + val antialiasArray = if (antialias) intArrayOf( + NSOpenGLPFAMultisample, + NSOpenGLPFASampleBuffers, 1, + NSOpenGLPFASamples, 4 + ) else intArrayOf() + intArrayOf( + *antialiasArray, + //NSOpenGLPFAOpenGLProfile, + //NSOpenGLProfileVersion4_1Core, + NSOpenGLPFADoubleBuffer, + NSOpenGLPFAColorSize, 24, + NSOpenGLPFAAlphaSize, 8, + NSOpenGLPFADepthSize, 24, + NSOpenGLPFAStencilSize, 8, + NSOpenGLPFAAccumSize, 0, + 0 + ) + } + + val NSThread = NSClass("NSThread") + val NSObject = NSClass("NSObject") + val pixelFormat = NSClass("NSOpenGLPixelFormat").alloc().msgSend("initWithAttributes:", attrs) + val openGLContext = NSClass("NSOpenGLContext").alloc().msgSend("initWithFormat:shareContext:", pixelFormat, sharedContext) + + init { + //println("pixelFormat: $pixelFormat") + //println("openGLContext: $openGLContext") + if (contentView != 0L) setView(contentView) + } + + override val scaleFactor: Double get() = if (window != 0L) window.msgSendCGFloat("backingScaleFactor").toDouble() else 1.0 + + override fun makeCurrent() { + openGLContext.msgSend("makeCurrentContext") + //println("MacosGLContext.makeCurrentContext: ${openGLContext.msgSend("view")}") + } + + override fun swapBuffers() { + GL.glFlush() + openGLContext.msgSend("flushBuffer") + } + + fun clearDrawable() { + openGLContext.msgSend("clearDrawable") + } + + + + fun setView(contentView: Long) { + runOnMainThread { + println("MacosGLContext.setView: $contentView") + openGLContext.msgSend("setView:", contentView) + + this.contentView = openGLContext.msgSend("view") + + //println(contentView.msgSendNSRect("frame")) + //println(contentView.msgSend("window").msgSendNSRect("frame")) + } + } + + var callback: (() -> Unit)? = null + val isMainThread: Boolean get() = NSThread.msgSend("isMainThread") != 0L + val MyThreadExecutor = AllocateClassAndRegister("MyThreadExecutor", "NSObject") { + addMethod("main:", ObjcCallbackVoid { self, _sel, sender -> + //println("MyThreadExecutor") + callback?.invoke() + callback = null + }, "v@:@") + } + val myThreadExecutorInstance = MyThreadExecutor.alloc().msgSend("init") + + fun runOnMainThread(block: () -> Unit) { + if (isMainThread) { + block() + } else { + synchronized(this) { + //println("isMainThread: $isMainThread") + callback = block + myThreadExecutorInstance.msgSend( + "performSelectorOnMainThread:withObject:waitUntilDone:", + sel("main:"), + null, + 1 + ) + } + } + } + + override fun useContext(g: Graphics, ag: AG, action: (Graphics, BaseOpenglContext.ContextInfo) -> Unit) { + //runOnMainThread { + run { + makeCurrent() + try { + val factor = 2.0 // @TODO: + val frame = contentView.msgSendNSRect("frame") + val fx = (frame.x * factor).toInt() + val fy = (frame.y * factor).toInt() + val fw = (frame.width * factor).toInt() + val fh = (frame.height * factor).toInt() + // factor=2.0, scissorBox: java.awt.Rectangle[x=0,y=0,width=2560,height=1496], viewport: java.awt.Rectangle[x=0,y=0,width=2560,height=1496] + val info = BaseOpenglContext.ContextInfo(RectangleInt(), RectangleInt(),) + info.scissors?.setTo(fx, fy, fw, fh) + info.viewport?.setTo(fx, fy, fw, fh) + println("info=$info") + action(g, info) + } finally { + swapBuffers() + releaseCurrent() + } + } + } + + fun setParameters() { + val dims = intArrayOf(720, 480) + GL.CGLSetParameter(openGLContext, 304, dims) + GL.CGLEnable(openGLContext, 304) + } +} + +class MacAWTOpenglContext(val c: Component, var other: MacosGLContext? = null) : BaseOpenglContext { + companion object { + private inline fun privilegedAction(crossinline block: () -> T): T { + var result: T? = null + AccessController.doPrivileged(PrivilegedAction { + result = block() + }) + return result!! + } + } + + val utils = privilegedAction { Class.forName("sun.java2d.opengl.OGLUtilities") } + //println(utils.declaredMethods.map { it.name }) + val invokeWithOGLContextCurrentMethod = privilegedAction { + utils.getDeclaredMethod("invokeWithOGLContextCurrent", Graphics::class.java, Runnable::class.java).also { it.isAccessible = true } + } + val isQueueFlusherThread = privilegedAction { + utils.getDeclaredMethod("isQueueFlusherThread").also { it.isAccessible = true } + } + val getOGLViewport = privilegedAction { + utils.getDeclaredMethod("getOGLViewport", Graphics::class.java, Integer.TYPE, Integer.TYPE).also { it.isAccessible = true } + } + val getOGLScissorBox = privilegedAction { + utils.getDeclaredMethod("getOGLScissorBox", Graphics::class.java).also { it.isAccessible = true } + } + val getOGLSurfaceIdentifier = privilegedAction { + utils.getDeclaredMethod("getOGLSurfaceIdentifier", Graphics::class.java).also { it.isAccessible = true } + } + val getOGLSurfaceType = privilegedAction { + utils.getDeclaredMethod("getOGLSurfaceType", Graphics::class.java).also { it.isAccessible = true } + } + + val info = BaseOpenglContext.ContextInfo( + RectangleInt(), RectangleInt() + ) + + override val scaleFactor: Double get() = getDisplayScalingFactor(c) + + override fun useContext(g: Graphics, ag: AG, action: (Graphics, BaseOpenglContext.ContextInfo) -> Unit) { + invokeWithOGLContextCurrentMethod.invoke(null, g, Runnable { + //if (!(isQueueFlusherThread.invoke(null) as Boolean)) error("Can't render on another thread") + try { + val factor = getDisplayScalingFactor(c) + //val window = SwingUtilities.getWindowAncestor(c) + val viewport = getOGLViewport.invoke(null, g, (c.width * factor).toInt(), (c.height * factor).toInt()) as java.awt.Rectangle + //val viewport = getOGLViewport.invoke(null, g, window.width.toInt(), window.height.toInt()) as java.awt.Rectangle + val scissorBox = getOGLScissorBox(null, g) as? java.awt.Rectangle? + ///println("factor=$factor, scissorBox: $scissorBox, viewport: $viewport") + //info.scissors?.setTo(scissorBox.x, scissorBox.y, scissorBox.width, scissorBox.height) + if (scissorBox != null) { + info.scissors?.setTo(scissorBox.x, scissorBox.y, scissorBox.width, scissorBox.height) + //info.viewport?.setTo(viewport.x, viewport.y, viewport.width, viewport.height) + info.viewport?.setTo(scissorBox.x, scissorBox.y, scissorBox.width, scissorBox.height) + } else { + System.err.println("ERROR !! scissorBox = $scissorBox, viewport = $viewport") + } + //info.viewport?.setTo(scissorBox.x, scissorBox.y) + //println("viewport: $viewport, $scissorBox") + //println(g.clipBounds) + if (other != null) { + val oldContext = NSClass("NSOpenGLContext").msgSend("currentContext") + other!!.useContext(g, ag) { g, info -> action(g, info) } + oldContext.msgSend("makeCurrentContext") + } else { + action(g, info) + } + + } catch (e: Throwable) { + e.printStackTrace() + } + }) + } + + override fun makeCurrent() = Unit + override fun releaseCurrent() = Unit + override fun swapBuffers() = Unit +} + +class ProxiedMacAWTOpenglContext(val c: Component) : BaseOpenglContext { + override fun makeCurrent() { + } + + val cctx = MacAWTOpenglContext(c) + + override fun useContext(g: Graphics, ag: AG, action: (Graphics, BaseOpenglContext.ContextInfo) -> Unit) { + val gl = (ag as AGOpengl).gl + cctx.useContext(g, ag) { g, info -> + if (cctx.other == null) { + val peer = getComponentPeer(if (c is Window) c else SwingUtilities.getWindowAncestor(c)) + val platformWindow = peer.getOrThrow("platformWindow") + val nsWindowPtr = platformWindow.getOrThrow("ptr").long + val contentView = nsWindowPtr.msgSend("contentView") + + cctx.other = MacosGLContext(contentView, nsWindowPtr, sharedContext = NSClass("NSOpenGLContext").msgSend("currentContext")) + } + + val backBufferTextureBinding2d = gl.getIntegerv(gl.TEXTURE_BINDING_2D) + val backBufferRenderBufferBinding = gl.getIntegerv(gl.RENDERBUFFER_BINDING) + val backBufferFrameBufferBinding = gl.getIntegerv(gl.FRAMEBUFFER_BINDING) + + println("backBufferTextureBinding2d=$backBufferTextureBinding2d, $backBufferRenderBufferBinding, $backBufferFrameBufferBinding") + + /* + gl.bindTexture(gl.TEXTURE_2D, backBufferTextureBinding2d) + gl.bindRenderbuffer(gl.RENDERBUFFER, backBufferRenderBufferBinding) + gl.bindFramebuffer(gl.FRAMEBUFFER, backBufferFrameBufferBinding) + */ + + cctx.other!!.useContext(g, ag) { g, info -> action(g, info) } + } + } +} + +fun getComponentPeer(component: Component?): Dyn { + return Dyn.global["sun.awt.AWTAccessor"] + .dynamicInvokeOrThrow("getComponentAccessor") + .dynamicInvokeOrThrow("getPeer", component) +} diff --git a/korgw/src/jvmMain/kotlin/com/soywiz/korgw/osx/MacosGameWindow.kt b/korgw/src/jvmMain/kotlin/com/soywiz/korgw/osx/MacosGameWindow.kt index d96e6256c..a97573e3a 100644 --- a/korgw/src/jvmMain/kotlin/com/soywiz/korgw/osx/MacosGameWindow.kt +++ b/korgw/src/jvmMain/kotlin/com/soywiz/korgw/osx/MacosGameWindow.kt @@ -10,9 +10,7 @@ import com.soywiz.korev.KeyEvent import com.soywiz.korev.MouseButton import com.soywiz.korev.MouseEvent import com.soywiz.korgw.* -import com.soywiz.korgw.platform.BaseOpenglContext -import com.soywiz.korgw.platform.INativeGL -import com.soywiz.korgw.platform.NativeKgl +import com.soywiz.korgw.platform.* import com.soywiz.korgw.platform.NativeLoad import com.soywiz.korim.bitmap.Bitmap import com.soywiz.korim.format.PNG @@ -21,8 +19,7 @@ import com.soywiz.korio.file.VfsFile import com.soywiz.korio.net.URL import com.soywiz.korio.util.OS import com.soywiz.korio.util.Once -import com.sun.jna.Callback -import com.sun.jna.Library +import com.sun.jna.* import java.nio.ByteBuffer import kotlin.coroutines.* import kotlin.system.* @@ -35,7 +32,9 @@ class MacAG(val window: Long, val checkGl: Boolean, val logGl:Boolean) : AGOpeng override val nativeComponent: Any = window } -open class MacKmlGL : NativeKgl(MacGL) +//open class MacKmlGL : NativeKgl(MacGL) +open class MacKmlGL : NativeKgl(DirectGL) + interface MacGL : INativeGL, Library { fun CGLSetParameter(vararg args: Any?) @@ -177,14 +176,9 @@ class MacGameWindow(val checkGl: Boolean, val logGl: Boolean) : GameWindow() { val buttonNumber = sender.msgSend("buttonNumber") val clickCount = sender.msgSend("clickCount") - val rect = MyNSRect() - contentView.msgSend_stret(rect, "frame") - - val rect2 = MyNSRect() - window.msgSend_stret(rect2, "frame") - - val rect3 = MyNSRect() - window.msgSend_stret(rect3, "contentRectForFrameRect:", rect2) + val rect = contentView.msgSendNSRect("frame") + val rect2 = window.msgSendNSRect("frame") + val rect3 = window.msgSendNSRect("contentRectForFrameRect:", rect2) glCtx?.setParameters() diff --git a/korgw/src/jvmMain/kotlin/com/soywiz/korgw/platform/BaseOpenglContext.kt b/korgw/src/jvmMain/kotlin/com/soywiz/korgw/platform/BaseOpenglContext.kt index d2cd35a36..02e952e2e 100644 --- a/korgw/src/jvmMain/kotlin/com/soywiz/korgw/platform/BaseOpenglContext.kt +++ b/korgw/src/jvmMain/kotlin/com/soywiz/korgw/platform/BaseOpenglContext.kt @@ -2,6 +2,7 @@ package com.soywiz.korgw.platform import com.soywiz.korag.* import com.soywiz.korgw.awt.* +import com.soywiz.korgw.osx.* import com.soywiz.korgw.win32.Win32OpenglContext import com.soywiz.korgw.x11.X import com.soywiz.korgw.x11.X11OpenglContext @@ -22,7 +23,7 @@ import javax.swing.* interface BaseOpenglContext : Disposable { val isCore: Boolean get() = false val scaleFactor: Double get() = 1.0 - class ContextInfo( + data class ContextInfo( val scissors: RectangleInt? = null, val viewport: RectangleInt? = null ) { @@ -77,81 +78,11 @@ object DummyOpenglContext : BaseOpenglContext { } } -private inline fun privilegedAction(crossinline block: () -> T): T { - var result: T? = null - AccessController.doPrivileged(PrivilegedAction { - result = block() - }) - return result!! -} - fun glContextFromComponent(c: Component): BaseOpenglContext { return when { OS.isMac -> { try { - val utils = privilegedAction { Class.forName("sun.java2d.opengl.OGLUtilities") } - //println(utils.declaredMethods.map { it.name }) - val invokeWithOGLContextCurrentMethod = privilegedAction { - utils.getDeclaredMethod("invokeWithOGLContextCurrent", Graphics::class.java, Runnable::class.java).also { it.isAccessible = true } - } - val isQueueFlusherThread = privilegedAction { - utils.getDeclaredMethod("isQueueFlusherThread").also { it.isAccessible = true } - } - val getOGLViewport = privilegedAction { - utils.getDeclaredMethod("getOGLViewport", Graphics::class.java, Integer.TYPE, Integer.TYPE).also { it.isAccessible = true } - } - val getOGLScissorBox = privilegedAction { - utils.getDeclaredMethod("getOGLScissorBox", Graphics::class.java).also { it.isAccessible = true } - } - val getOGLSurfaceIdentifier = privilegedAction { - utils.getDeclaredMethod("getOGLSurfaceIdentifier", Graphics::class.java).also { it.isAccessible = true } - } - val getOGLSurfaceType = privilegedAction { - utils.getDeclaredMethod("getOGLSurfaceType", Graphics::class.java).also { it.isAccessible = true } - } - - val info = BaseOpenglContext.ContextInfo( - RectangleInt(), RectangleInt() - ) - - //var timeSinceLast = 0L - object : BaseOpenglContext { - override val scaleFactor: Double get() = getDisplayScalingFactor(c) - - override fun useContext(g: Graphics, ag: AG, action: (Graphics, BaseOpenglContext.ContextInfo) -> Unit) { - invokeWithOGLContextCurrentMethod.invoke(null, g, Runnable { - //if (!(isQueueFlusherThread.invoke(null) as Boolean)) error("Can't render on another thread") - try { - val factor = getDisplayScalingFactor(c) - //val window = SwingUtilities.getWindowAncestor(c) - val viewport = getOGLViewport.invoke(null, g, (c.width * factor).toInt(), (c.height * factor).toInt()) as java.awt.Rectangle - //val viewport = getOGLViewport.invoke(null, g, window.width.toInt(), window.height.toInt()) as java.awt.Rectangle - val scissorBox = getOGLScissorBox(null, g) as? java.awt.Rectangle? - //println("scissorBox: $scissorBox") - //println("viewport: $viewport") - //info.scissors?.setTo(scissorBox.x, scissorBox.y, scissorBox.width, scissorBox.height) - if (scissorBox != null) { - info.scissors?.setTo(scissorBox.x, scissorBox.y, scissorBox.width, scissorBox.height) - //info.viewport?.setTo(viewport.x, viewport.y, viewport.width, viewport.height) - info.viewport?.setTo(scissorBox.x, scissorBox.y, scissorBox.width, scissorBox.height) - } else { - System.err.println("ERROR !! scissorBox = $scissorBox, viewport = $viewport") - } - //info.viewport?.setTo(scissorBox.x, scissorBox.y) - //println("viewport: $viewport, $scissorBox") - //println(g.clipBounds) - action(g, info) - - } catch (e: Throwable) { - e.printStackTrace() - } - }) - } - - override fun makeCurrent() = Unit - override fun releaseCurrent() = Unit - override fun swapBuffers() = Unit - } + MacAWTOpenglContext(c) } catch (e: Throwable) { e.printStackTrace() System.err.println("Might require run the JVM with --add-opens=java.desktop/sun.java2d.opengl=ALL-UNNAMED") diff --git a/korgw/src/jvmMain/kotlin/com/soywiz/korgw/platform/INativeGL.kt b/korgw/src/jvmMain/kotlin/com/soywiz/korgw/platform/INativeGL.kt index 0106deaaa..ab2c885b5 100644 --- a/korgw/src/jvmMain/kotlin/com/soywiz/korgw/platform/INativeGL.kt +++ b/korgw/src/jvmMain/kotlin/com/soywiz/korgw/platform/INativeGL.kt @@ -1,8 +1,11 @@ +@file:Suppress("unused") + package com.soywiz.korgw.platform -import com.soywiz.kmem.FBuffer -import com.soywiz.korim.bitmap.NativeImage -import com.sun.jna.NativeLong +import com.soywiz.korio.lang.* +import com.soywiz.korio.time.* +import com.soywiz.korio.util.* +import com.sun.jna.* import java.nio.ByteBuffer import java.nio.FloatBuffer import java.nio.IntBuffer @@ -34,6 +37,201 @@ typealias VoidPtr = ByteBuffer typealias IntPtr = IntBuffer typealias FloatPtr = FloatBuffer +object DirectGL : INativeGL { + external override fun glActiveTexture(texture: GLenum) + external override fun glAttachShader(program: GLuint, shader: GLuint) + external override fun glBindAttribLocation(program: GLuint, index: GLuint, name: String) + external override fun glBindBuffer(target: GLenum, buffer: GLuint) + external override fun glBindFramebuffer(target: GLenum, framebuffer: GLuint) + external override fun glBindRenderbuffer(target: GLenum, renderbuffer: GLuint) + external override fun glBindTexture(target: GLenum, texture: GLuint) + external override fun glBlendColor(red: GLfloat, green: GLfloat, blue: GLfloat, alpha: GLfloat) + external override fun glBlendEquation(mode: GLenum) + external override fun glBlendEquationSeparate(modeRGB: GLenum, modeAlpha: GLenum) + external override fun glBlendFunc(sfactor: GLenum, dfactor: GLenum) + external override fun glBlendFuncSeparate(sfactorRGB: GLenum, dfactorRGB: GLenum, sfactorAlpha: GLenum, dfactorAlpha: GLenum) + external override fun glBufferData(target: GLenum, size: GLsizeiptr, data: VoidPtr, usage: GLenum) + external override fun glBufferSubData(target: GLenum, offset: GLintptr, size: GLsizeiptr, data: VoidPtr) + external override fun glCheckFramebufferStatus(target: GLenum): GLenum + external override fun glClear(mask: GLbitfield) + external override fun glClearColor(red: GLfloat, green: GLfloat, blue: GLfloat, alpha: GLfloat) + external override fun glClearDepth(d: GLdouble) + external override fun glClearStencil(s: GLint) + external override fun glColorMask(red: GLboolean, green: GLboolean, blue: GLboolean, alpha: GLboolean) + external override fun glCompileShader(shader: GLuint) + external override fun glCompressedTexImage2D(target: GLenum, level: GLint, internalformat: GLenum, width: GLsizei, height: GLsizei, border: GLint, imageSize: GLsizei, data: VoidPtr) + external override fun glCompressedTexSubImage2D(target: GLenum, level: GLint, xoffset: GLint, yoffset: GLint, width: GLsizei, height: GLsizei, format: GLenum, imageSize: GLsizei, data: VoidPtr) + external override fun glCopyTexImage2D(target: GLenum, level: GLint, internalformat: GLenum, x: GLint, y: GLint, width: GLsizei, height: GLsizei, border: GLint) + external override fun glCopyTexSubImage2D(target: GLenum, level: GLint, xoffset: GLint, yoffset: GLint, x: GLint, y: GLint, width: GLsizei, height: GLsizei) + external override fun glCreateProgram(): GLuint + external override fun glCreateShader(type: GLenum): GLuint + external override fun glCullFace(mode: GLenum) + external override fun glDeleteBuffers(n: GLsizei, items: IntPtr) + external override fun glDeleteFramebuffers(n: GLsizei, items: IntPtr) + external override fun glDeleteProgram(program: GLuint) + external override fun glDeleteRenderbuffers(n: GLsizei, items: IntPtr) + external override fun glDeleteShader(shader: GLuint) + external override fun glDeleteTextures(n: GLsizei, items: IntPtr) + external override fun glDepthFunc(func: GLenum) + external override fun glDepthMask(flag: GLboolean) + external override fun glDepthRange(n: GLdouble, f: GLdouble) + external override fun glDetachShader(program: GLuint, shader: GLuint) + external override fun glDisable(cap: GLenum) + external override fun glDisableVertexAttribArray(index: GLuint) + external override fun glDrawArrays(mode: GLenum, first: GLint, count: GLsizei) + external override fun glDrawElements(mode: GLenum, count: GLsizei, type: GLenum, indices: IntSize) + external override fun glEnable(cap: GLenum) + external override fun glEnableVertexAttribArray(index: GLuint) + external override fun glFinish() + external override fun glFlush() + external override fun glFramebufferRenderbuffer(target: GLenum, attachment: GLenum, renderbuffertarget: GLenum, renderbuffer: GLuint) + external override fun glFramebufferTexture2D(target: GLenum, attachment: GLenum, textarget: GLenum, texture: GLuint, level: GLint) + external override fun glFrontFace(mode: GLenum) + external override fun glGenBuffers(n: GLsizei, buffers: IntPtr) + external override fun glGenerateMipmap(target: GLenum) + external override fun glGenFramebuffers(n: GLsizei, framebuffers: IntPtr) + external override fun glGenRenderbuffers(n: GLsizei, renderbuffers: IntPtr) + external override fun glGenTextures(n: GLsizei, textures: IntPtr) + external override fun glGetActiveAttrib(program: GLuint, index: GLuint, bufSize: GLsizei, length: IntPtr, size: IntPtr, type: IntPtr, name: VoidPtr) + external override fun glGetActiveUniform(program: GLuint, index: GLuint, bufSize: GLsizei, length: IntPtr, size: IntPtr, type: IntPtr, name: VoidPtr) + external override fun glGetAttachedShaders(program: GLuint, maxCount: GLsizei, count: IntPtr, shaders: IntPtr) + external override fun glGetAttribLocation(program: GLuint, name: String): Int + external override fun glGetUniformLocation(program: GLuint, name: String): Int + external override fun glGetBooleanv(pname: GLenum, data: VoidPtr) + external override fun glGetBufferParameteriv(target: GLenum, pname: GLenum, params: IntPtr) + external override fun glGetError(): GLenum + external override fun glGetFloatv(pname: GLenum, data: FloatPtr) + external override fun glGetFramebufferAttachmentParameteriv(target: GLenum, attachment: GLenum, pname: GLenum, params: IntPtr) + external override fun glGetIntegerv(pname: GLenum, data: IntPtr) + external override fun glGetProgramInfoLog(program: GLuint, bufSize: GLsizei, length: IntPtr, infoLog: VoidPtr) + external override fun glGetRenderbufferParameteriv(target: GLenum, pname: GLenum, params: IntPtr) + external override fun glGetProgramiv(program: GLuint, pname: GLenum, params: IntPtr) + external override fun glGetShaderiv(shader: GLuint, pname: GLenum, params: IntPtr) + external override fun glGetShaderInfoLog(shader: GLuint, bufSize: GLsizei, length: IntPtr, infoLog: VoidPtr) + external override fun glGetShaderPrecisionFormat(shadertype: GLenum, precisiontype: GLenum, range: IntPtr, precision: IntPtr) + external override fun glGetShaderSource(shader: GLuint, bufSize: GLsizei, length: IntPtr, source: VoidPtr) + external override fun glGetString(name: GLenum): String? + external override fun glGetStringi(name: GLenum, i: GLuint): String? + external override fun glGetTexParameterfv(target: GLenum, pname: GLenum, params: FloatPtr) + external override fun glGetTexParameteriv(target: GLenum, pname: GLenum, params: IntPtr) + external override fun glGetUniformfv(program: GLuint, location: GLint, params: FloatPtr) + external override fun glGetUniformiv(program: GLuint, location: GLint, params: IntPtr) + external override fun glGetVertexAttribfv(index: GLuint, pname: GLenum, params: FloatPtr) + external override fun glGetVertexAttribiv(index: GLuint, pname: GLenum, params: IntPtr) + external override fun glHint(target: GLenum, mode: GLenum) + external override fun glIsBuffer(buffer: GLuint): GLboolean + external override fun glIsEnabled(cap: GLenum): GLboolean + external override fun glIsFramebuffer(framebuffer: GLuint): GLboolean + external override fun glIsProgram(program: GLuint): GLboolean + external override fun glIsRenderbuffer(renderbuffer: GLuint): GLboolean + external override fun glIsShader(shader: GLuint): GLboolean + external override fun glIsTexture(texture: GLuint): GLboolean + external override fun glLineWidth(width: GLfloat) + external override fun glLinkProgram(program: GLuint) + external override fun glPixelStorei(pname: GLenum, param: GLint) + external override fun glPolygonOffset(factor: GLfloat, units: GLfloat) + external override fun glReadPixels(x: GLint, y: GLint, width: GLsizei, height: GLsizei, format: GLenum, type: GLenum, pixels: VoidPtr) + external override fun glReleaseShaderCompiler() + external override fun glRenderbufferStorage(target: GLenum, internalformat: GLenum, width: GLsizei, height: GLsizei) + external override fun glSampleCoverage(value: GLfloat, invert: GLboolean) + external override fun glScissor(x: GLint, y: GLint, width: GLsizei, height: GLsizei) + external override fun glShaderBinary(count: GLsizei, shaders: IntPtr, binaryformat: GLenum, binary: VoidPtr, length: GLsizei) + external fun glShaderSource(shader: GLuint, count: GLsizei, strings: Pointer, length: IntArray?) + override fun glShaderSource(shader: GLuint, count: GLsizei, string: Array, length: IntArray?) { + val ptrs = Memory((Native.POINTER_SIZE * string.size).toLong()) + for (n in 0 until string.size) { + val bytes = string[n].toByteArray(UTF8) + val mem = Memory(bytes.size.toLong()) + mem.write(0L, bytes, 0, bytes.size) + ptrs.setPointer((Native.POINTER_SIZE * n).toLong(), mem) + } + glShaderSource(shader, count, ptrs, length) + ptrs.clear() + } + + //@JvmStatic external fun glShaderSource(shader: GLuint, count: GLsizei, string: Array, length: IntArray?) + external override fun glStencilFunc(func: GLenum, ref: GLint, mask: GLuint) + external override fun glStencilFuncSeparate(face: GLenum, func: GLenum, ref: GLint, mask: GLuint) + external override fun glStencilMask(mask: GLuint) + external override fun glStencilMaskSeparate(face: GLenum, mask: GLuint) + external override fun glStencilOp(fail: GLenum, zfail: GLenum, zpass: GLenum) + external override fun glStencilOpSeparate(face: GLenum, sfail: GLenum, dpfail: GLenum, dppass: GLenum) + external override fun glTexImage2D(target: GLenum, level: GLint, internalformat: GLint, width: GLsizei, height: GLsizei, border: GLint, format: GLenum, type: GLenum, pixels: VoidPtr?) + external override fun glTexParameterf(target: GLenum, pname: GLenum, param: GLfloat) + external override fun glTexParameterfv(target: GLenum, pname: GLenum, params: FloatPtr) + external override fun glTexParameteri(target: GLenum, pname: GLenum, param: GLint) + external override fun glTexParameteriv(target: GLenum, pname: GLenum, params: IntPtr) + external override fun glTexSubImage2D(target: GLenum, level: GLint, xoffset: GLint, yoffset: GLint, width: GLsizei, height: GLsizei, format: GLenum, type: GLenum, pixels: VoidPtr) + external override fun glUniform1f(location: GLint, v0: GLfloat) + external override fun glUniform1fv(location: GLint, count: GLsizei, value: FloatPtr) + external override fun glUniform1i(location: GLint, v0: GLint) + external override fun glUniform1iv(location: GLint, count: GLsizei, value: IntPtr) + external override fun glUniform2f(location: GLint, v0: GLfloat, v1: GLfloat) + external override fun glUniform2fv(location: GLint, count: GLsizei, value: FloatPtr) + external override fun glUniform2i(location: GLint, v0: GLint, v1: GLint) + external override fun glUniform2iv(location: GLint, count: GLsizei, value: IntPtr) + external override fun glUniform3f(location: GLint, v0: GLfloat, v1: GLfloat, v2: GLfloat) + external override fun glUniform3fv(location: GLint, count: GLsizei, value: FloatPtr) + external override fun glUniform3i(location: GLint, v0: GLint, v1: GLint, v2: GLint) + external override fun glUniform3iv(location: GLint, count: GLsizei, value: IntPtr) + external override fun glUniform4f(location: GLint, v0: GLfloat, v1: GLfloat, v2: GLfloat, v3: GLfloat) + external override fun glUniform4fv(location: GLint, count: GLsizei, value: FloatPtr) + external override fun glUniform4i(location: GLint, v0: GLint, v1: GLint, v2: GLint, v3: GLint) + external override fun glUniform4iv(location: GLint, count: GLsizei, value: IntPtr) + external override fun glUniformMatrix2fv(location: GLint, count: GLsizei, transpose: GLboolean, value: FloatPtr) + external override fun glUniformMatrix3fv(location: GLint, count: GLsizei, transpose: GLboolean, value: FloatPtr) + external override fun glUniformMatrix4fv(location: GLint, count: GLsizei, transpose: GLboolean, value: FloatPtr) + external override fun glUseProgram(program: GLuint) + external override fun glValidateProgram(program: GLuint) + external override fun glVertexAttrib1f(index: GLuint, x: GLfloat) + external override fun glVertexAttrib1fv(index: GLuint, v: FloatPtr) + external override fun glVertexAttrib2f(index: GLuint, x: GLfloat, y: GLfloat) + external override fun glVertexAttrib2fv(index: GLuint, v: FloatPtr) + external override fun glVertexAttrib3f(index: GLuint, x: GLfloat, y: GLfloat, z: GLfloat) + external override fun glVertexAttrib3fv(index: GLuint, v: FloatPtr) + external override fun glVertexAttrib4f(index: GLuint, x: GLfloat, y: GLfloat, z: GLfloat, w: GLfloat) + external override fun glVertexAttrib4fv(index: GLuint, v: FloatPtr) + external override fun glVertexAttribPointer(index: GLuint, size: GLint, type: GLenum, normalized: GLboolean, stride: GLsizei, pointer: IntSize) + external override fun glViewport(x: GLint, y: GLint, width: GLsizei, height: GLsizei) + external override fun glDrawArraysInstanced(mode: GLenum, first: GLint, count: GLsizei, instancecount: GLsizei) + external override fun glDrawElementsInstanced(mode: GLenum, count: GLsizei, type: GLenum, indices: IntSize, instancecount: GLsizei) + external override fun glVertexAttribDivisor(index: GLuint, divisor: GLuint) + external override fun glRenderbufferStorageMultisample(target: GLenum, samples: GLint, internalformat: GLenum, width: GLsizei, height: GLsizei) + + internal var loaded = false + + init { + try { + if (nativeOpenGLLibraryPath == null) error("Can't get OpenGL library") + traceTime("OpenGL Native.register") { + // @TODO: Can we provide a custom loader? like a dysym, to use the glGetProcAddress? If so, we will be able to use this on Windows too + Native.register(DirectGL::class.java, NativeLibrary.getInstance(nativeOpenGLLibraryPath, mutableMapOf())) + } + loaded = true + } catch (e: Throwable) { + com.soywiz.klogger.Console.error("Failed to initialize OpenAL: arch=$arch, OS.rawName=${OS.rawName}, nativeOpenGLLibraryPath=$nativeOpenGLLibraryPath, message=${e.message}") + //e.printStackTrace() + } + } +} + +private val arch by lazy { System.getProperty("os.arch").toLowerCase() } + +val nativeOpenGLLibraryPath: String? by lazy { + Environment["OPENGL_LIB_PATH"]?.let { path -> + return@lazy path + } + when { + OS.isMac -> "OpenGL" + OS.isLinux -> "libGL" + OS.isWindows -> "opengl32" + else -> { + println(" - Unknown/Unsupported OS") + null + } + } +} + interface INativeGL { fun glActiveTexture(texture: GLenum) fun glAttachShader(program: GLuint, shader: GLuint) @@ -115,7 +313,7 @@ interface INativeGL { fun glGetUniformiv(program: GLuint, location: GLint, params: IntPtr) fun glGetVertexAttribfv(index: GLuint, pname: GLenum, params: FloatPtr) fun glGetVertexAttribiv(index: GLuint, pname: GLenum, params: IntPtr) - fun glGetVertexAttribPointerv(index: GLuint, pname: GLenum, pointer: FBuffer) + //fun glGetVertexAttribPointerv(index: GLuint, pname: GLenum, pointer: FBuffer) fun glHint(target: GLenum, mode: GLenum) fun glIsBuffer(buffer: GLuint): GLboolean fun glIsEnabled(cap: GLenum): GLboolean @@ -142,7 +340,7 @@ interface INativeGL { fun glStencilOp(fail: GLenum, zfail: GLenum, zpass: GLenum) fun glStencilOpSeparate(face: GLenum, sfail: GLenum, dpfail: GLenum, dppass: GLenum) fun glTexImage2D(target: GLenum, level: GLint, internalformat: GLint, width: GLsizei, height: GLsizei, border: GLint, format: GLenum, type: GLenum, pixels: VoidPtr?) - fun glTexImage2D(target: GLenum, level: GLint, internalformat: GLint, format: GLsizei, type: GLsizei, data: NativeImage) + //fun glTexImage2D(target: GLenum, level: GLint, internalformat: GLint, format: GLsizei, type: GLsizei, data: NativeImage) fun glTexParameterf(target: GLenum, pname: GLenum, param: GLfloat) fun glTexParameterfv(target: GLenum, pname: GLenum, params: FloatPtr) fun glTexParameteri(target: GLenum, pname: GLenum, param: GLint) @@ -180,9 +378,10 @@ interface INativeGL { fun glVertexAttribPointer(index: GLuint, size: GLint, type: GLenum, normalized: GLboolean, stride: GLsizei, pointer: IntSize) fun glViewport(x: GLint, y: GLint, width: GLsizei, height: GLsizei) - fun glDrawArraysInstanced(mode: GLenum, first: GLint, count: GLsizei, instancecount: GLsizei) + fun glDrawArraysInstanced(mode: GLenum, first: GLint, count: GLsizei, instancecount: GLsizei): Unit fun glDrawElementsInstanced(mode: GLenum, count: GLsizei, type: GLenum, indices: IntSize, instancecount: GLsizei) fun glVertexAttribDivisor(index: GLuint, divisor: GLuint) + fun glRenderbufferStorageMultisample(target: GLenum, samples: GLint, internalformat: GLenum, width: GLsizei, height: GLsizei) companion object { const val DEPTH_BUFFER_BIT: Int = 0x0100 @@ -488,378 +687,3 @@ interface INativeGL { const val INVALID_FRAMEBUFFER_OPERATION: Int = 0x0506 // 1286 } } - -/* - fun glActiveTexture(texture: Int) - fun glAttachShader(program: Int, shader: Int) - fun glBindAttribLocation(program: Int, index: Int, name: String) - fun glBindBuffer(target: Int, buffer: Int) - fun glBindFramebuffer(target: Int, framebuffer: Int) - fun glBindRenderbuffer(target: Int, renderbuffer: Int) - fun glBindTexture(target: Int, texture: Int) - fun glBlendColor(red: Float, green: Float, blue: Float, alpha: Float) - fun glBlendEquation(mode: Int) - fun glBlendEquationSeparate(modeRGB: Int, modeAlpha: Int) - fun glBlendFunc(sfactor: Int, dfactor: Int) - fun glBlendFuncSeparate(sfactorRGB: Int, dfactorRGB: Int, sfactorAlpha: Int, dfactorAlpha: Int) - fun glBufferData(target: Int, size: IntSize, data: VoidPtr, usage: Int) - fun glBufferSubData(target: Int, offset: IntSize, size: IntSize, data: VoidPtr) - fun glCheckFramebufferStatus(target: Int): Int - fun glClear(mask: Int) - fun glClearColor(red: Float, green: Float, blue: Float, alpha: Float) - fun glClearDepth(d: Double) - fun glClearStencil(s: Int) - fun glColorMask(red: Boolean, green: Boolean, blue: Boolean, alpha: Boolean) - fun glCompileShader(shader: Int) - fun glCompressedTexImage2D( - target: Int, - level: Int, - internalformat: Int, - width: Int, - height: Int, - border: Int, - imageSize: Int, - data: VoidPtr - ) - - fun glCompressedTexSubImage2D( - target: Int, - level: Int, - xoffset: Int, - yoffset: Int, - width: Int, - height: Int, - format: Int, - imageSize: Int, - data: VoidPtr - ) - - fun glCopyTexImage2D( - target: Int, - level: Int, - internalformat: Int, - x: Int, - y: Int, - width: Int, - height: Int, - border: Int - ) - - fun glCopyTexSubImage2D( - target: Int, - level: Int, - xoffset: Int, - yoffset: Int, - x: Int, - y: Int, - width: Int, - height: Int - ) - - fun glCreateProgram(): Int - fun glCreateShader(type: Int): Int - fun glCullFace(mode: Int) - fun glDeleteBuffers(n: Int, items: IntPtr) - fun glDeleteFramebuffers(n: Int, items: IntPtr) - fun glDeleteProgram(program: Int) - fun glDeleteRenderbuffers(n: Int, items: IntPtr) - fun glDeleteShader(shader: Int) - fun glDeleteTextures(n: Int, items: IntPtr) - fun glDepthFunc(func: Int) - fun glDepthMask(flag: Boolean) - fun glDepthRange(n: Double, f: Double) - fun glDetachShader(program: Int, shader: Int) - fun glDisable(cap: Int) - fun glDisableVertexAttribArray(index: Int) - fun glDrawArrays(mode: Int, first: Int, count: Int) - fun glDrawElements(mode: Int, count: Int, type: Int, indices: IntSize) - fun glEnable(cap: Int) - fun glEnableVertexAttribArray(index: Int) - fun glFinish() - fun glFlush() - fun glFramebufferRenderbuffer(target: Int, attachment: Int, renderbuffertarget: Int, renderbuffer: Int) - fun glFramebufferTexture2D(target: Int, attachment: Int, textarget: Int, texture: Int, level: Int) - fun glFrontFace(mode: Int) - fun glGenBuffers(n: Int, buffers: IntPtr) - fun glGenerateMipmap(target: Int) - fun glGenFramebuffers(n: Int, framebuffers: IntPtr) - fun glGenRenderbuffers(n: Int, renderbuffers: IntPtr) - fun glGenTextures(n: Int, textures: IntPtr) - fun glGetActiveAttrib( - program: Int, - index: Int, - bufSize: Int, - length: IntPtr, - size: IntPtr, - type: IntPtr, - name: VoidPtr - ) - - fun glGetActiveUniform( - program: Int, - index: Int, - bufSize: Int, - length: IntPtr, - size: IntPtr, - type: IntPtr, - name: VoidPtr - ) - - fun glGetAttachedShaders(program: Int, maxCount: Int, count: IntPtr, shaders: IntPtr) - fun glGetAttribLocation(program: Int, name: String): Int - fun glGetUniformLocation(program: Int, name: String): Int - fun glGetBooleanv(pname: Int, data: VoidPtr) - fun glGetBufferParameteriv(target: Int, pname: Int, params: IntPtr) - fun glGetError(): Int - fun glGetFloatv(pname: Int, data: FloatPtr) - fun glGetFramebufferAttachmentParameteriv(target: Int, attachment: Int, pname: Int, params: IntPtr) - fun glGetIntegerv(pname: Int, data: IntPtr) - fun glGetProgramInfoLog(program: Int, bufSize: Int, length: IntPtr, infoLog: VoidPtr) - fun glGetRenderbufferParameteriv(target: Int, pname: Int, params: IntPtr) - fun glGetProgramiv(program: Int, pname: Int, params: IntPtr) - fun glGetShaderiv(shader: Int, pname: Int, params: IntPtr) - fun glGetShaderInfoLog(shader: Int, bufSize: Int, length: IntPtr, infoLog: VoidPtr) - fun glGetShaderPrecisionFormat(shadertype: Int, precisiontype: Int, range: IntPtr, precision: IntPtr) - fun glGetShaderSource(shader: Int, bufSize: Int, length: IntPtr, source: VoidPtr) - fun glGetString(name: Int): String - fun glGetTexParameterfv(target: Int, pname: Int, params: FloatPtr) - fun glGetTexParameteriv(target: Int, pname: Int, params: IntPtr) - fun glGetUniformfv(program: Int, location: Int, params: FloatPtr) - fun glGetUniformiv(program: Int, location: Int, params: IntPtr) - fun glGetVertexAttribfv(index: Int, pname: Int, params: FloatPtr) - fun glGetVertexAttribiv(index: Int, pname: Int, params: IntPtr) - fun glGetVertexAttribPointerv(index: Int, pname: Int, pointer: FBuffer) - fun glHint(target: Int, mode: Int) - fun glIsBuffer(buffer: Int): Boolean - fun glIsEnabled(cap: Int): Boolean - fun glIsFramebuffer(framebuffer: Int): Boolean - fun glIsProgram(program: Int): Boolean - fun glIsRenderbuffer(renderbuffer: Int): Boolean - fun glIsShader(shader: Int): Boolean - fun glIsTexture(texture: Int): Boolean - fun glLineWidth(width: Float) - fun glLinkProgram(program: Int) - fun glPixelStorei(pname: Int, param: Int) - fun glPolygonOffset(factor: Float, units: Float) - fun glReadPixels(x: Int, y: Int, width: Int, height: Int, format: Int, type: Int, pixels: VoidPtr) - fun glReleaseShaderCompiler() - fun glRenderbufferStorage(target: Int, internalformat: Int, width: Int, height: Int) - fun glSampleCoverage(value: Float, invert: Boolean) - fun glScissor(x: Int, y: Int, width: Int, height: Int) - fun glShaderBinary(count: Int, shaders: IntPtr, binaryformat: Int, binary: VoidPtr, length: Int) - fun glShaderSource(shader: Int, count: IntSize, string: Array, length: IntArray?) - fun glStencilFunc(func: Int, ref: Int, mask: Int) - fun glStencilFuncSeparate(face: Int, func: Int, ref: Int, mask: Int) - fun glStencilMask(mask: Int) - fun glStencilMaskSeparate(face: Int, mask: Int) - fun glStencilOp(fail: Int, zfail: Int, zpass: Int) - fun glStencilOpSeparate(face: Int, sfail: Int, dpfail: Int, dppass: Int) - fun glTexImage2D( - target: Int, - level: Int, - internalformat: Int, - width: Int, - height: Int, - border: Int, - format: Int, - type: Int, - pixels: VoidPtr? - ) - - fun glTexImage2D(target: Int, level: Int, internalformat: Int, format: Int, type: Int, data: NativeImage) - fun glTexParameterf(target: Int, pname: Int, param: Float) - fun glTexParameterfv(target: Int, pname: Int, params: FloatPtr) - fun glTexParameteri(target: Int, pname: Int, param: Int) - fun glTexParameteriv(target: Int, pname: Int, params: IntPtr) - fun glTexSubImage2D( - target: Int, - level: Int, - xoffset: Int, - yoffset: Int, - width: Int, - height: Int, - format: Int, - type: Int, - pixels: VoidPtr - ) - - fun glUniform1f(location: Int, v0: Float) - fun glUniform1fv(location: Int, count: Int, value: FloatPtr) - fun glUniform1i(location: Int, v0: Int) - fun glUniform1iv(location: Int, count: Int, value: IntPtr) - fun glUniform2f(location: Int, v0: Float, v1: Float) - fun glUniform2fv(location: Int, count: Int, value: FloatPtr) - fun glUniform2i(location: Int, v0: Int, v1: Int) - fun glUniform2iv(location: Int, count: Int, value: IntPtr) - fun glUniform3f(location: Int, v0: Float, v1: Float, v2: Float) - fun glUniform3fv(location: Int, count: Int, value: FloatPtr) - fun glUniform3i(location: Int, v0: Int, v1: Int, v2: Int) - fun glUniform3iv(location: Int, count: Int, value: IntPtr) - fun glUniform4f(location: Int, v0: Float, v1: Float, v2: Float, v3: Float) - fun glUniform4fv(location: Int, count: Int, value: FloatPtr) - fun glUniform4i(location: Int, v0: Int, v1: Int, v2: Int, v3: Int) - fun glUniform4iv(location: Int, count: Int, value: IntPtr) - fun glUniformMatrix2fv(location: Int, count: Int, transpose: Boolean, value: FloatPtr) - fun glUniformMatrix3fv(location: Int, count: Int, transpose: Boolean, value: FloatPtr) - fun glUniformMatrix4fv(location: Int, count: Int, transpose: Boolean, value: FloatPtr) - fun glUseProgram(program: Int) - fun glValidateProgram(program: Int) - fun glVertexAttrib1f(index: Int, x: Float) - fun glVertexAttrib1fv(index: Int, v: FloatPtr) - fun glVertexAttrib2f(index: Int, x: Float, y: Float) - fun glVertexAttrib2fv(index: Int, v: FloatPtr) - fun glVertexAttrib3f(index: Int, x: Float, y: Float, z: Float) - fun glVertexAttrib3fv(index: Int, v: FloatPtr) - fun glVertexAttrib4f(index: Int, x: Float, y: Float, z: Float, w: Float) - fun glVertexAttrib4fv(index: Int, v: FloatPtr) - fun glVertexAttribPointer(index: Int, size: Int, type: Int, normalized: Boolean, stride: Int, pointer: IntSize) - fun glViewport(x: Int, y: Int, width: Int, height: Int) - */ - -/* - - fun glActiveTexture(texture: Int) - fun glAttachShader(program: Int, shader: Int) - fun glBindAttribLocation(program: Int, index: Int, name: String) - fun glBindBuffer(target: Int, buffer: Int) - fun glBindFramebuffer(target: Int, framebuffer: Int) - fun glBindRenderbuffer(target: Int, renderbuffer: Int) - fun glBindTexture(target: Int, texture: Int) - fun glBlendColor(red: Float, green: Float, blue: Float, alpha: Float) - fun glBlendEquation(mode: Int) - fun glBlendEquationSeparate(modeRGB: Int, modeAlpha: Int) - fun glBlendFunc(sfactor: Int, dfactor: Int) - fun glBlendFuncSeparate(sfactorRGB: Int, dfactorRGB: Int, sfactorAlpha: Int, dfactorAlpha: Int) - fun glBufferData(target: Int, size: IntSize, data: VoidPtr, usage: Int) - fun glBufferSubData(target: Int, offset: IntSize, size: IntSize, data: VoidPtr) - fun glCheckFramebufferStatus(target: Int): Int - fun glClear(mask: Int) - fun glClearColor(red: Float, green: Float, blue: Float, alpha: Float) - fun glClearDepth(d: Double) - fun glClearStencil(s: Int) - fun glColorMask(red: Boolean, green: Boolean, blue: Boolean, alpha: Boolean) - fun glCompileShader(shader: Int) - fun glCompressedTexImage2D(target: Int, level: Int, internalformat: Int, width: Int, height: Int, border: Int, imageSize: Int, data: VoidPtr) - fun glCompressedTexSubImage2D(target: Int, level: Int, xoffset: Int, yoffset: Int, width: Int, height: Int, format: Int, imageSize: Int, data: VoidPtr) - fun glCopyTexImage2D(target: Int, level: Int, internalformat: Int, x: Int, y: Int, width: Int, height: Int, border: Int) - fun glCopyTexSubImage2D(target: Int, level: Int, xoffset: Int, yoffset: Int, x: Int, y: Int, width: Int, height: Int) - fun glCreateProgram(): Int - fun glCreateShader(type: Int): Int - fun glCullFace(mode: Int) - fun glDeleteBuffers(n: Int, items: IntPtr) - fun glDeleteFramebuffers(n: Int, items: IntPtr) - fun glDeleteProgram(program: Int) - fun glDeleteRenderbuffers(n: Int, items: IntPtr) - fun glDeleteShader(shader: Int) - fun glDeleteTextures(n: Int, items: IntPtr) - fun glDepthFunc(func: Int) - fun glDepthMask(flag: Boolean) - fun glDepthRange(n: Double, f: Double) - fun glDetachShader(program: Int, shader: Int) - fun glDisable(cap: Int) - fun glDisableVertexAttribArray(index: Int) - fun glDrawArrays(mode: Int, first: Int, count: Int) - fun glDrawElements(mode: Int, count: Int, type: Int, indices: IntSize) - fun glEnable(cap: Int) - fun glEnableVertexAttribArray(index: Int) - fun glFinish() - fun glFlush() - fun glFramebufferRenderbuffer(target: Int, attachment: Int, renderbuffertarget: Int, renderbuffer: Int) - fun glFramebufferTexture2D(target: Int, attachment: Int, textarget: Int, texture: Int, level: Int) - fun glFrontFace(mode: Int) - fun glGenBuffers(n: Int, buffers: IntPtr) - fun glGenerateMipmap(target: Int) - fun glGenFramebuffers(n: Int, framebuffers: IntPtr) - fun glGenRenderbuffers(n: Int, renderbuffers: IntPtr) - fun glGenTextures(n: Int, textures: IntPtr) - fun glGetActiveAttrib(program: Int, index: Int, bufSize: Int, length: IntPtr, size: IntPtr, type: IntPtr, name: VoidPtr) - fun glGetActiveUniform(program: Int, index: Int, bufSize: Int, length: IntPtr, size: IntPtr, type: IntPtr, name: VoidPtr) - fun glGetAttachedShaders(program: Int, maxCount: Int, count: IntPtr, shaders: IntPtr) - fun glGetAttribLocation(program: Int, name: String): Int - fun glGetUniformLocation(program: Int, name: String): Int - fun glGetBooleanv(pname: Int, data: VoidPtr) - fun glGetBufferParameteriv(target: Int, pname: Int, params: IntPtr) - fun glGetError(): Int - fun glGetFloatv(pname: Int, data: FloatPtr) - fun glGetFramebufferAttachmentParameteriv(target: Int, attachment: Int, pname: Int, params: IntPtr) - fun glGetIntegerv(pname: Int, data: IntPtr) - fun glGetProgramInfoLog(program: Int, bufSize: Int, length: IntPtr, infoLog: VoidPtr) - fun glGetRenderbufferParameteriv(target: Int, pname: Int, params: IntPtr) - fun glGetProgramiv(program: Int, pname: Int, params: IntPtr) - fun glGetShaderiv(shader: Int, pname: Int, params: IntPtr) - fun glGetShaderInfoLog(shader: Int, bufSize: Int, length: IntPtr, infoLog: VoidPtr) - fun glGetShaderPrecisionFormat(shadertype: Int, precisiontype: Int, range: IntPtr, precision: IntPtr) - fun glGetShaderSource(shader: Int, bufSize: Int, length: IntPtr, source: VoidPtr) - fun glGetString(name: Int): String? - fun glGetTexParameterfv(target: Int, pname: Int, params: FloatPtr) - fun glGetTexParameteriv(target: Int, pname: Int, params: IntPtr) - fun glGetUniformfv(program: Int, location: Int, params: FloatPtr) - fun glGetUniformiv(program: Int, location: Int, params: IntPtr) - fun glGetVertexAttribfv(index: Int, pname: Int, params: FloatPtr) - fun glGetVertexAttribiv(index: Int, pname: Int, params: IntPtr) - fun glGetVertexAttribPointerv(index: Int, pname: Int, pointer: FBuffer) - fun glHint(target: Int, mode: Int) - fun glIsBuffer(buffer: Int): Boolean - fun glIsEnabled(cap: Int): Boolean - fun glIsFramebuffer(framebuffer: Int): Boolean - fun glIsProgram(program: Int): Boolean - fun glIsRenderbuffer(renderbuffer: Int): Boolean - fun glIsShader(shader: Int): Boolean - fun glIsTexture(texture: Int): Boolean - fun glLineWidth(width: Float) - fun glLinkProgram(program: Int) - fun glPixelStorei(pname: Int, param: Int) - fun glPolygonOffset(factor: Float, units: Float) - fun glReadPixels(x: Int, y: Int, width: Int, height: Int, format: Int, type: Int, pixels: VoidPtr) - fun glReleaseShaderCompiler() - fun glRenderbufferStorage(target: Int, internalformat: Int, width: Int, height: Int) - fun glSampleCoverage(value: Float, invert: Boolean) - fun glScissor(x: Int, y: Int, width: Int, height: Int) - fun glShaderBinary(count: Int, shaders: IntPtr, binaryformat: Int, binary: VoidPtr, length: Int) - fun glShaderSource(shader: Int, count: IntSize, string: Array, length: IntArray?) - fun glStencilFunc(func: Int, ref: Int, mask: Int) - fun glStencilFuncSeparate(face: Int, func: Int, ref: Int, mask: Int) - fun glStencilMask(mask: Int) - fun glStencilMaskSeparate(face: Int, mask: Int) - fun glStencilOp(fail: Int, zfail: Int, zpass: Int) - fun glStencilOpSeparate(face: Int, sfail: Int, dpfail: Int, dppass: Int) - fun glTexImage2D(target: Int, level: Int, internalformat: Int, width: Int, height: Int, border: Int, format: Int, type: Int, pixels: VoidPtr?) - fun glTexImage2D(target: Int, level: Int, internalformat: Int, format: Int, type: Int, data: NativeImage) - fun glTexParameterf(target: Int, pname: Int, param: Float) - fun glTexParameterfv(target: Int, pname: Int, params: FloatPtr) - fun glTexParameteri(target: Int, pname: Int, param: Int) - fun glTexParameteriv(target: Int, pname: Int, params: IntPtr) - fun glTexSubImage2D(target: Int, level: Int, xoffset: Int, yoffset: Int, width: Int, height: Int, format: Int, type: Int, pixels: VoidPtr) - fun glUniform1f(location: Int, v0: Float) - fun glUniform1fv(location: Int, count: Int, value: FloatPtr) - fun glUniform1i(location: Int, v0: Int) - fun glUniform1iv(location: Int, count: Int, value: IntPtr) - fun glUniform2f(location: Int, v0: Float, v1: Float) - fun glUniform2fv(location: Int, count: Int, value: FloatPtr) - fun glUniform2i(location: Int, v0: Int, v1: Int) - fun glUniform2iv(location: Int, count: Int, value: IntPtr) - fun glUniform3f(location: Int, v0: Float, v1: Float, v2: Float) - fun glUniform3fv(location: Int, count: Int, value: FloatPtr) - fun glUniform3i(location: Int, v0: Int, v1: Int, v2: Int) - fun glUniform3iv(location: Int, count: Int, value: IntPtr) - fun glUniform4f(location: Int, v0: Float, v1: Float, v2: Float, v3: Float) - fun glUniform4fv(location: Int, count: Int, value: FloatPtr) - fun glUniform4i(location: Int, v0: Int, v1: Int, v2: Int, v3: Int) - fun glUniform4iv(location: Int, count: Int, value: IntPtr) - fun glUniformMatrix2fv(location: Int, count: Int, transpose: Boolean, value: FloatPtr) - fun glUniformMatrix3fv(location: Int, count: Int, transpose: Boolean, value: FloatPtr) - fun glUniformMatrix4fv(location: Int, count: Int, transpose: Boolean, value: FloatPtr) - fun glUseProgram(program: Int) - fun glValidateProgram(program: Int) - fun glVertexAttrib1f(index: Int, x: Float) - fun glVertexAttrib1fv(index: Int, v: FloatPtr) - fun glVertexAttrib2f(index: Int, x: Float, y: Float) - fun glVertexAttrib2fv(index: Int, v: FloatPtr) - fun glVertexAttrib3f(index: Int, x: Float, y: Float, z: Float) - fun glVertexAttrib3fv(index: Int, v: FloatPtr) - fun glVertexAttrib4f(index: Int, x: Float, y: Float, z: Float, w: Float) - fun glVertexAttrib4fv(index: Int, v: FloatPtr) - fun glVertexAttribPointer(index: Int, size: Int, type: Int, normalized: Boolean, stride: Int, pointer: IntSize) - fun glViewport(x: Int, y: Int, width: Int, height: Int) - - */ diff --git a/korgw/src/jvmMain/kotlin/com/soywiz/korgw/platform/NativeKgl.kt b/korgw/src/jvmMain/kotlin/com/soywiz/korgw/platform/NativeKgl.kt index 6ee7181ae..35426053f 100644 --- a/korgw/src/jvmMain/kotlin/com/soywiz/korgw/platform/NativeKgl.kt +++ b/korgw/src/jvmMain/kotlin/com/soywiz/korgw/platform/NativeKgl.kt @@ -6,7 +6,7 @@ import com.soywiz.korim.awt.AwtNativeImage import com.soywiz.korim.bitmap.NativeImage import com.sun.jna.NativeLong -open class NativeKgl(val gl: INativeGL) : KmlGlWithExtensions() { +open class NativeKgl(private val gl: INativeGL) : KmlGlWithExtensions() { override fun activeTexture(texture: Int): Unit = gl.glActiveTexture(texture) override fun attachShader(program: Int, shader: Int): Unit = gl.glAttachShader(program, shader) override fun bindAttribLocation(program: Int, index: Int, name: String): Unit = gl.glBindAttribLocation(program, index, name) @@ -105,6 +105,7 @@ open class NativeKgl(val gl: INativeGL) : KmlGlWithExtensions() { override fun sampleCoverage(value: Float, invert: Boolean): Unit = gl.glSampleCoverage(value, invert.toByte()) override fun scissor(x: Int, y: Int, width: Int, height: Int): Unit = gl.glScissor(x, y, width, height) override fun shaderBinary(count: Int, shaders: FBuffer, binaryformat: Int, binary: FBuffer, length: Int): Unit = gl.glShaderBinary(count, shaders.nioIntBuffer, binaryformat, binary.nioBuffer, length) + //override fun shaderSource(shader: Int, string: String): Unit = gl.glShaderSource(shader, 1, arrayOf(string), intArrayOf(string.length)) override fun shaderSource(shader: Int, string: String): Unit = gl.glShaderSource(shader, 1, arrayOf(string), intArrayOf(string.length)) override fun stencilFunc(func: Int, ref: Int, mask: Int): Unit = gl.glStencilFunc(func, ref, mask) override fun stencilFuncSeparate(face: Int, func: Int, ref: Int, mask: Int): Unit = gl.glStencilFuncSeparate(face, func, ref, mask) @@ -135,9 +136,9 @@ open class NativeKgl(val gl: INativeGL) : KmlGlWithExtensions() { override fun uniform4fv(location: Int, count: Int, value: FBuffer): Unit = gl.glUniform4fv(location, count, value.nioFloatBuffer) override fun uniform4i(location: Int, v0: Int, v1: Int, v2: Int, v3: Int): Unit = gl.glUniform4i(location, v0, v1, v2, v3) override fun uniform4iv(location: Int, count: Int, value: FBuffer): Unit = gl.glUniform4iv(location, count, value.nioIntBuffer) - override fun uniformMatrix2fv(location: Int, count: Int, transpose: Boolean, value: FBuffer): Unit = gl.glUniformMatrix2fv(location, count, transpose.toByte(), value.nioFloatBuffer) - override fun uniformMatrix3fv(location: Int, count: Int, transpose: Boolean, value: FBuffer): Unit = gl.glUniformMatrix3fv(location, count, transpose.toByte(), value.nioFloatBuffer) - override fun uniformMatrix4fv(location: Int, count: Int, transpose: Boolean, value: FBuffer): Unit = gl.glUniformMatrix4fv(location, count, transpose.toByte(), value.nioFloatBuffer) + override fun uniformMatrix2fv(location: Int, count: Int, transpose: Boolean, value: FBuffer): Unit = gl.glUniformMatrix2fv(location, count, transpose.toByte(), value.nioFloatBuffer.slice(0, 4 * count)) + override fun uniformMatrix3fv(location: Int, count: Int, transpose: Boolean, value: FBuffer): Unit = gl.glUniformMatrix3fv(location, count, transpose.toByte(), value.nioFloatBuffer.slice(0, 9 * count)) + override fun uniformMatrix4fv(location: Int, count: Int, transpose: Boolean, value: FBuffer): Unit = gl.glUniformMatrix4fv(location, count, transpose.toByte(), value.nioFloatBuffer.slice(0, 16 * count)) override fun useProgram(program: Int): Unit = gl.glUseProgram(program) override fun validateProgram(program: Int): Unit = gl.glValidateProgram(program) override fun vertexAttrib1f(index: Int, x: Float): Unit = gl.glVertexAttrib1f(index, x) @@ -157,6 +158,10 @@ open class NativeKgl(val gl: INativeGL) : KmlGlWithExtensions() { override fun drawArraysInstanced(mode: Int, first: Int, count: Int, instancecount: Int): Unit = gl.glDrawArraysInstanced(mode, first, count, instancecount) override fun drawElementsInstanced(mode: Int, count: Int, type: Int, indices: Int, instancecount: Int): Unit = gl.glDrawElementsInstanced(mode, count, type, NativeLong(indices.toLong()), instancecount) override fun vertexAttribDivisor(index: Int, divisor: Int): Unit = gl.glVertexAttribDivisor(index, divisor) + override fun renderbufferStorageMultisample(target: Int, samples: Int, internalformat: Int, width: Int, height: Int) { + gl.glRenderbufferStorageMultisample(target, samples, internalformat, width, height) + } } + private const val GL_NUM_EXTENSIONS = 0x821D diff --git a/korgw/src/macosMain/kotlin/com/soywiz/kgl/KmlGlNative.kt b/korgw/src/macosMain/kotlin/com/soywiz/kgl/KmlGlNative.kt index f6036a79c..e20c83249 100644 --- a/korgw/src/macosMain/kotlin/com/soywiz/kgl/KmlGlNative.kt +++ b/korgw/src/macosMain/kotlin/com/soywiz/kgl/KmlGlNative.kt @@ -174,4 +174,5 @@ actual class KmlGlNative actual constructor() : NativeBaseKmlGl() { override fun drawArraysInstanced(mode: Int, first: Int, count: Int, instancecount: Int) = glDrawArraysInstancedARB(mode.convert(), first, count, instancecount) override fun drawElementsInstanced(mode: Int, count: Int, type: Int, indices: Int, instancecount: Int) = glDrawElementsInstancedARB(mode.convert(), count, type.convert(), indices.toLong().toCPointer(), instancecount) override fun vertexAttribDivisor(index: Int, divisor: Int) = glVertexAttribDivisorARB(index.convert(), divisor.convert()) + override fun renderbufferStorageMultisample(target: Int, samples: Int, internalformat: Int, width: Int, height: Int) = glRenderbufferStorageMultisample(target.convert(), samples, internalformat.convert(), width, height) } diff --git a/korim/src/commonMain/kotlin/com/soywiz/korim/color/RGBAf.kt b/korim/src/commonMain/kotlin/com/soywiz/korim/color/RGBAf.kt index 558fb34f3..7180e41e6 100644 --- a/korim/src/commonMain/kotlin/com/soywiz/korim/color/RGBAf.kt +++ b/korim/src/commonMain/kotlin/com/soywiz/korim/color/RGBAf.kt @@ -19,6 +19,20 @@ class RGBAf( private var dirty = true + fun readFrom(out: FloatArray, index: Int = 0) { + r = out[index + 0] + g = out[index + 1] + b = out[index + 2] + a = out[index + 3] + } + + fun writeTo(out: FloatArray, index: Int = 0) { + out[index + 0] = r + out[index + 1] = g + out[index + 2] = b + out[index + 3] = a + } + var r: Float; get() = _r; set(v) { _r = v; makeDirty() } var g: Float; get() = _g; set(v) { _g = v; makeDirty() } var b: Float; get() = _b; set(v) { _b = v; makeDirty() } @@ -93,3 +107,10 @@ class RGBAf( } inline fun RGBAf(r: Number, g: Number, b: Number, a: Number) = RGBAf(r.toFloat(), g.toFloat(), b.toFloat(), a.toFloat()) + +fun RGBA.writeFloat(out: FloatArray, index: Int = 0) { + out[index + 0] = rf + out[index + 1] = gf + out[index + 2] = bf + out[index + 3] = af +} diff --git a/korim/src/commonMain/kotlin/com/soywiz/korim/paint/Filler.kt b/korim/src/commonMain/kotlin/com/soywiz/korim/paint/Filler.kt index 35935e51a..37cf69a46 100644 --- a/korim/src/commonMain/kotlin/com/soywiz/korim/paint/Filler.kt +++ b/korim/src/commonMain/kotlin/com/soywiz/korim/paint/Filler.kt @@ -51,8 +51,8 @@ class BitmapFiller : BaseFiller() { fill.transform.inverted(this.fillTrans) compTrans.apply { identity() - multiply(this, stateTrans) - multiply(this, fillTrans) + premultiply(fillTrans) + premultiply(stateTrans) } } @@ -86,41 +86,31 @@ class BitmapFiller : BaseFiller() { } class GradientFiller : BaseFiller() { - private val NCOLORS = 256 + companion object { + const val NCOLORS = 256 + } private val colors = RgbaPremultipliedArray(NCOLORS) private lateinit var fill: GradientPaint - private val stateInv: Matrix = Matrix() - - private fun stopN(n: Int): Int = (fill.stops[n] * NCOLORS).toInt() + private val stateTrans = Matrix() + private val fillTrans = Matrix() + private val compTrans = Matrix() fun set(fill: GradientPaint, state: Context2d.State) = this.apply { this.fill = fill - state.transform.inverted(this.stateInv) - - when (fill.numberOfStops) { - 0, 1 -> { - val color = if (fill.numberOfStops == 0) Colors.FUCHSIA else RGBA(fill.colors.first()) - val pcolor = color.premultiplied - for (n in 0 until NCOLORS) colors[n] = pcolor - } - else -> { - for (n in 0 until stopN(0)) colors[n] = RGBA(fill.colors.first()).premultiplied - for (n in 0 until fill.numberOfStops - 1) { - val stop0 = stopN(n + 0) - val stop1 = stopN(n + 1) - val color0 = RGBA(fill.colors.getAt(n + 0)) - val color1 = RGBA(fill.colors.getAt(n + 1)) - for (s in stop0 until stop1) { - val ratio = (s - stop0).toDouble() / (stop1 - stop0).toDouble() - colors[s] = RGBA.interpolate(color0, color1, ratio).premultiplied - } - } - for (n in stopN(fill.numberOfStops - 1) until NCOLORS) colors.ints[n] = fill.colors.last() - } + state.transform.inverted(this.stateTrans) + fill.transform.inverted(this.fillTrans) + compTrans.apply { + identity() + premultiply(fillTrans) + premultiply(stateTrans) } + + fill.fillColors(colors) + //println("colors=$colors") } private fun color(ratio: Double): RGBAPremultiplied { + //println("ratio=$ratio") return colors[(ratio.clamp01() * (NCOLORS - 1)).toInt()] } @@ -128,6 +118,6 @@ class GradientFiller : BaseFiller() { // @TODO: This doesn't seems to work proprely override fun fill(data: RgbaPremultipliedArray, offset: Int, x0: Int, x1: Int, y: Int) { //for (n in x0..x1) data[n] = color(mat.transformX(n.toDouble(), y.toDouble()).clamp01()) - for (n in x0..x1) data[offset + n] = color(fill.getRatioAt(n.toDouble(), y.toDouble(), stateInv)) + for (n in x0..x1) data[offset + n] = color(fill.getRatioAt(n.toDouble(), y.toDouble(), compTrans)) } } diff --git a/korim/src/commonMain/kotlin/com/soywiz/korim/paint/Paint.kt b/korim/src/commonMain/kotlin/com/soywiz/korim/paint/Paint.kt index ddf291cef..0596b0d43 100644 --- a/korim/src/commonMain/kotlin/com/soywiz/korim/paint/Paint.kt +++ b/korim/src/commonMain/kotlin/com/soywiz/korim/paint/Paint.kt @@ -4,8 +4,7 @@ import com.soywiz.kds.DoubleArrayList import com.soywiz.kds.IntArrayList import com.soywiz.kmem.clamp import com.soywiz.korim.bitmap.Bitmap -import com.soywiz.korim.color.Colors -import com.soywiz.korim.color.RGBA +import com.soywiz.korim.color.* import com.soywiz.korim.vector.* import com.soywiz.korma.geom.* import kotlin.apply @@ -87,8 +86,38 @@ data class GradientPaint( fun fromGradientBox(kind: GradientKind, width: Double, height: Double, rotation: Angle, tx: Double, ty: Double): GradientPaint { return identity(kind).copy(transform = gradientBoxMatrix(width, height, rotation, tx, ty)) } + + fun fillColors(out: RgbaPremultipliedArray, stops: DoubleArrayList, colors: IntArrayList) { + val numberOfStops = stops.size + val NCOLORS = out.size + fun stopN(n: Int): Int = (stops[n] * NCOLORS).toInt() + + when (numberOfStops) { + 0, 1 -> { + val color = if (numberOfStops == 0) Colors.FUCHSIA else RGBA(colors.first()) + val pcolor = color.premultiplied + for (n in 0 until NCOLORS) out[n] = pcolor + } + else -> { + for (n in 0 until stopN(0)) out[n] = RGBA(colors.first()).premultiplied + for (n in 0 until numberOfStops - 1) { + val stop0 = stopN(n + 0) + val stop1 = stopN(n + 1) + val color0 = RGBA(colors.getAt(n + 0)) + val color1 = RGBA(colors.getAt(n + 1)) + for (s in stop0 until stop1) { + val ratio = (s - stop0).toDouble() / (stop1 - stop0).toDouble() + out[s] = RGBA.interpolate(color0, color1, ratio).premultiplied + } + } + for (n in stopN(numberOfStops - 1) until NCOLORS) out.ints[n] = colors.last() + } + } + } } + fun fillColors(out: RgbaPremultipliedArray): Unit = fillColors(out, stops, colors) + fun addColorStop(stop: Double, color: RGBA): GradientPaint = add(stop, color) inline fun addColorStop(stop: Number, color: RGBA): GradientPaint = add(stop.toDouble(), color) diff --git a/korim/src/commonMain/kotlin/com/soywiz/korim/vector/CycleMethod.kt b/korim/src/commonMain/kotlin/com/soywiz/korim/vector/CycleMethod.kt index d3b66fec9..effc634a5 100644 --- a/korim/src/commonMain/kotlin/com/soywiz/korim/vector/CycleMethod.kt +++ b/korim/src/commonMain/kotlin/com/soywiz/korim/vector/CycleMethod.kt @@ -9,9 +9,9 @@ enum class CycleMethod { fun apply(ratio: Double, clamp: Boolean = false): Double = when (this) { NO_CYCLE -> if (clamp) ratio.clamp01() else ratio - REPEAT -> ratio % 1 + REPEAT -> ratio umod 1.0 REFLECT -> { - val part = ratio % 2 + val part = ratio umod 2.0 if (part > 1.0) 2.0 - part else part } } diff --git a/korim/src/commonMain/kotlin/com/soywiz/korim/vector/Shape.kt b/korim/src/commonMain/kotlin/com/soywiz/korim/vector/Shape.kt index 314e56d42..c0477bc54 100644 --- a/korim/src/commonMain/kotlin/com/soywiz/korim/vector/Shape.kt +++ b/korim/src/commonMain/kotlin/com/soywiz/korim/vector/Shape.kt @@ -3,7 +3,7 @@ package com.soywiz.korim.vector import com.soywiz.kds.iterators.fastForEach import com.soywiz.korim.bitmap.toUri import com.soywiz.korim.color.RGBA -import com.soywiz.korim.font.Font +import com.soywiz.korim.font.* import com.soywiz.korim.paint.* import com.soywiz.korim.text.HorizontalAlign import com.soywiz.korim.text.VerticalAlign @@ -119,10 +119,11 @@ interface Shape : BoundsDrawable { fun containsPoint(x: Double, y: Double): Boolean = bounds.contains(x, y) } -fun Shape.getBounds(out: Rectangle = Rectangle()) = out.apply { - val bb = BoundsBuilder() +fun Shape.getBounds(out: Rectangle = Rectangle(), bb: BoundsBuilder = BoundsBuilder()): Rectangle { + bb.reset() addBounds(bb) bb.getBounds(out) + return out } fun Shape.toSvg(scale: Double = 1.0): Xml = SvgBuilder(this.getBounds(), scale).apply { buildSvg(this) }.toXml() @@ -420,6 +421,16 @@ class TextShape( if (stroke != null) c.strokeText(text, x, y) } } + + val primitiveShapes: Shape by lazy { + buildShape { + this.transform(this@TextShape.transform) + this.clip(this@TextShape.clip) + if (fill != null) font?.drawText(this, fontSize, text, fill, x, y, fill = true) + if (stroke != null) font?.drawText(this, fontSize, text, stroke, x, y, fill = false) + } + } + override fun buildSvg(svg: SvgBuilder) { svg.nodes += Xml.Tag( "text", mapOf( diff --git a/korim/src/commonMain/kotlin/com/soywiz/korim/vector/ShapeBuilder.kt b/korim/src/commonMain/kotlin/com/soywiz/korim/vector/ShapeBuilder.kt index e7dc06a2d..0e5b904b4 100644 --- a/korim/src/commonMain/kotlin/com/soywiz/korim/vector/ShapeBuilder.kt +++ b/korim/src/commonMain/kotlin/com/soywiz/korim/vector/ShapeBuilder.kt @@ -7,7 +7,8 @@ import com.soywiz.korim.vector.renderer.* import com.soywiz.korma.geom.* import com.soywiz.korma.geom.vector.* -inline fun buildShape(width: Int? = null, height: Int? = null, builder: ShapeBuilder.() -> Unit): Shape = ShapeBuilder(width, height).apply(builder).buildShape() +inline fun buildShape(width: Int? = null, height: Int? = null, builder: ShapeBuilder.() -> Unit): Shape = + ShapeBuilder(width, height).apply(builder).buildShape() open class ShapeBuilder(width: Int?, height: Int?) : Context2d(DummyRenderer), Drawable { override val rendererWidth: Int = width ?: 256 @@ -86,7 +87,7 @@ open class ShapeBuilder(width: Int?, height: Int?) : Context2d(DummyRenderer), D state.clone() shapes.clear() } - fun buildShape(): Shape = CompoundShape(shapes.toList()) + fun buildShape(): Shape = if (shapes.size == 1) shapes.first() else CompoundShape(shapes.toList()) override fun draw(c: Context2d) { c.draw(buildShape()) diff --git a/korio/src/commonMain/kotlin/com/soywiz/korio/dynamic/Dyn.kt b/korio/src/commonMain/kotlin/com/soywiz/korio/dynamic/Dyn.kt index 9650bf8c2..7088e108e 100644 --- a/korio/src/commonMain/kotlin/com/soywiz/korio/dynamic/Dyn.kt +++ b/korio/src/commonMain/kotlin/com/soywiz/korio/dynamic/Dyn.kt @@ -206,8 +206,11 @@ inline class Dyn(val value: Any?) : Comparable { } operator fun get(key: Dyn): Dyn = get(key.value) - operator fun get(key: Any?): Dyn = when (value) { - null -> null.dyn + operator fun get(key: Any?): Dyn = _getOrThrow(key, doThrow = false) + fun getOrThrow(key: Any?): Dyn = _getOrThrow(key, doThrow = true) + + private fun _getOrThrow(key: Any?, doThrow: Boolean): Dyn = when (value) { + null -> if (doThrow) throw NullPointerException("Trying to access '$key'") else null.dyn is Map<*, *> -> (value as Map)[key].dyn is List<*> -> value[key.dyn.toInt()].dyn else -> dynApi.get(value, key.toString()).dyn diff --git a/korio/src/commonMain/kotlin/com/soywiz/korio/dynamic/DynApi.kt b/korio/src/commonMain/kotlin/com/soywiz/korio/dynamic/DynApi.kt index b0f6f2799..ead02fe61 100644 --- a/korio/src/commonMain/kotlin/com/soywiz/korio/dynamic/DynApi.kt +++ b/korio/src/commonMain/kotlin/com/soywiz/korio/dynamic/DynApi.kt @@ -8,6 +8,8 @@ interface DynApi { fun get(instance: Any?, key: String): Any? fun set(instance: Any?, key: String, value: Any?) fun invoke(instance: Any?, key: String, args: Array): Any? + + fun getOrThrow(instance: Any?, key: String): Any? = get(instance, key) fun invokeOrThrow(instance: Any?, key: String, args: Array): Any? = invoke(instance, key, args) suspend fun suspendGet(instance: Any?, key: String): Any? = get(instance, key) diff --git a/korio/src/jvmAndroidMain/kotlin/com/soywiz/korio/dynamic/DynApiJvm.kt b/korio/src/jvmAndroidMain/kotlin/com/soywiz/korio/dynamic/DynApiJvm.kt index 272e8b8e5..7d9a05eef 100644 --- a/korio/src/jvmAndroidMain/kotlin/com/soywiz/korio/dynamic/DynApiJvm.kt +++ b/korio/src/jvmAndroidMain/kotlin/com/soywiz/korio/dynamic/DynApiJvm.kt @@ -42,30 +42,7 @@ internal actual object DynamicInternal : DynApi { } } - override fun get(instance: Any?, key: String): Any? { - if (instance == null) return null - - val static = instance is Class<*> - val clazz: Class<*> = if (static) instance as Class<*> else instance.javaClass - - if (instance is JavaPackage) { - val path = "${instance.name}.$key".trim('.') - return try { - java.lang.Class.forName(path) - } catch (e: ClassNotFoundException) { - JavaPackage(path) - } - } - val method = tryGetMethod(clazz, "get${key.capitalize()}", null) - if (method != null) { - return method.invoke(if (static) null else instance) - } - val field = tryGetField(clazz, key) - if (field != null) { - return field.get(if (static) null else instance) - } - return null - } + override fun get(instance: Any?, key: String): Any? = getBase(instance, key, doThrow = false) override fun set(instance: Any?, key: String, value: Any?) { if (instance == null) return @@ -85,6 +62,10 @@ internal actual object DynamicInternal : DynApi { } } + override fun getOrThrow(instance: Any?, key: String): Any? { + return getBase(instance, key, doThrow = true) + } + override fun invoke(instance: Any?, key: String, args: Array): Any? { return invokeBase(instance, key, args, doThrow = false) } @@ -93,6 +74,37 @@ internal actual object DynamicInternal : DynApi { return invokeBase(instance, key, args, doThrow = true) } + fun getBase(instance: Any?, key: String, doThrow: Boolean): Any? { + if (instance == null) { + if (doThrow) error("Can't get '$key' on null") + return null + } + + val static = instance is Class<*> + val clazz: Class<*> = if (static) instance as Class<*> else instance.javaClass + + if (instance is JavaPackage) { + val path = "${instance.name}.$key".trim('.') + return try { + java.lang.Class.forName(path) + } catch (e: ClassNotFoundException) { + JavaPackage(path) + } + } + val method = tryGetMethod(clazz, "get${key.capitalize()}", null) + if (method != null) { + return method.invoke(if (static) null else instance) + } + val field = tryGetField(clazz, key) + if (field != null) { + return field.get(if (static) null else instance) + } + if (doThrow) { + error("Can't find suitable fields or getters for '$key'") + } + return null + } + fun invokeBase(instance: Any?, key: String, args: Array, doThrow: Boolean): Any? { if (instance == null) { if (doThrow) error("Can't invoke '$key' on null") diff --git a/korma/src/commonMain/kotlin/com/soywiz/korma/geom/shape/Shape2d.kt b/korma/src/commonMain/kotlin/com/soywiz/korma/geom/shape/Shape2d.kt index 6731588ef..f1a1f6f7b 100644 --- a/korma/src/commonMain/kotlin/com/soywiz/korma/geom/shape/Shape2d.kt +++ b/korma/src/commonMain/kotlin/com/soywiz/korma/geom/shape/Shape2d.kt @@ -368,6 +368,9 @@ fun VectorPath.getPoints2(out: PointArrayList = PointArrayList()): PointArrayLis inline fun buildPath(out: VectorPath = VectorPath(), block: VectorPath.() -> Unit): VectorPath = out.apply(block) inline fun buildPath(out: VectorPath = VectorPath(), winding: Winding = Winding.EVEN_ODD, block: VectorPath.() -> Unit): VectorPath = out.also { it.winding = winding }.apply(block) +inline fun buildVectorPath(out: VectorPath = VectorPath(), block: VectorPath.() -> Unit): VectorPath = out.apply(block) +inline fun buildVectorPath(out: VectorPath = VectorPath(), winding: Winding = Winding.EVEN_ODD, block: VectorPath.() -> Unit): VectorPath = out.also { it.winding = winding }.apply(block) + inline fun approximateCurve( curveSteps: Int, crossinline compute: (ratio: Double, get: (x: Double, y: Double) -> Unit) -> Unit,