Skip to content

Commit

Permalink
Fixes OpenGL contextLost on all targets & checks on the JVM (#1520)
Browse files Browse the repository at this point in the history
* Checks OpenGL contextLost on JVM

* Improve Android pidof/logcat to ensure app start is logged

* Fixes context lost not updating setting data for buffers and potentially other objects

* Small fix
  • Loading branch information
soywiz authored Apr 12, 2023
1 parent c3be78c commit 452134d
Show file tree
Hide file tree
Showing 13 changed files with 192 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -78,17 +78,22 @@ fun Project.installAndroidRun(dependsOnList: List<String>, direct: Boolean, isKo
doFirst {
execLogger {
it.commandLine(
androidAdbPath, *extra, "shell", "am", "start", "-n",
"$androidApplicationId/$androidApplicationId.MainActivity"
androidAdbPath, *extra, "shell", "am", "start",
"-e", "sleepBeforeStart", "300",
"-n", "$androidApplicationId/$androidApplicationId.MainActivity"
)
}
val pid = run {
for (n in 0 until 10) {
val startTime = System.currentTimeMillis()
while (true) {
val currentTime = System.currentTimeMillis()
val elapsedTime = currentTime - startTime
try {
return@run execOutput(androidAdbPath, *extra, "shell", "pidof", androidApplicationId).trim()
} catch (e: Throwable) {
Thread.sleep(500L)
if (n == 9) throw e
//e.printStackTrace()
Thread.sleep(10L)
if (elapsedTime >= 5000L) throw e
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@ package korlibs.korge.render

import korlibs.datastructure.*
import korlibs.datastructure.iterators.*
import korlibs.logger.*
import korlibs.memory.*
import korlibs.graphics.*
import korlibs.graphics.shader.*
import korlibs.korge.internal.*
import korlibs.korge.view.*
import korlibs.image.bitmap.*
import korlibs.image.color.*
import korlibs.io.async.*
import korlibs.korge.internal.*
import korlibs.korge.view.*
import korlibs.logger.*
import korlibs.math.geom.*
import korlibs.memory.*
import kotlin.jvm.*
import kotlin.math.*
import kotlin.native.concurrent.*
Expand Down Expand Up @@ -124,6 +124,7 @@ class BatchBuilder2D constructor(
}

internal fun afterRender() {
//println("BatchBuilder2D.afterRender")
buffers.reset()
}

Expand Down
48 changes: 38 additions & 10 deletions korge/src/jvmMain/kotlin/korlibs/korge/testing/BitmapAsserter.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package korlibs.korge.testing

import korlibs.datastructure.*
import korlibs.korge.view.*
import korlibs.graphics.gl.*
import korlibs.image.awt.*
import korlibs.image.bitmap.*
import korlibs.image.color.*
import korlibs.image.format.*
import korlibs.io.lang.*
import korlibs.korge.view.*
import java.awt.*
import java.io.*
import javax.swing.*
Expand Down Expand Up @@ -92,6 +93,41 @@ private var OffscreenStage.testIndex: Int by extraProperty { 0 }
private fun Bitmap.toBitmap8Or32(): Bitmap = this.toBMP32().tryToExactBitmap8() ?: this
//private fun Bitmap.toBitmap8Or32(): Bitmap = this.toBMP32()

suspend fun OffscreenStage.simulateContextLost() {
//println("----simulateContextLost")
(views.ag as? AGOpengl?)?.let {
val fboWidth = ag.mainFrameBuffer.width
val fboHeight = ag.mainFrameBuffer.height
it.context = createKmlGlContext(fboWidth, fboHeight)
it.contextsToFree += it.context
it.context?.set()
ag.mainFrameBuffer.setSize(fboWidth, fboHeight)
it.contextLost()
}
}

suspend fun OffscreenStage.simulateRenderFrame(
view: View = this,
posterize: Int = 0,
includeBackground: Boolean = true,
useTexture: Boolean = true,
): Bitmap32 {
return views.ag.startEndFrame {
//val currentFrameBuffer = views.renderContext.currentFrameBuffer
//Bitmap32(currentFrameBuffer.width, currentFrameBuffer.height).also { ag.readColor(currentFrameBuffer, it) }
stage.views.renderContext.beforeRender()
try {
view.unsafeRenderToBitmapSync(
views.renderContext,
bgcolor = if (includeBackground) views.clearColor else Colors.TRANSPARENT,
useTexture = useTexture
).depremultiplied().posterizeInplace(posterize)
} finally {
stage.views.renderContext.afterRender()
}
}
}

suspend fun OffscreenStage.assertScreenshot(
view: View = this,
name: String = "$testIndex",
Expand All @@ -106,15 +142,7 @@ suspend fun OffscreenStage.assertScreenshot(
val interactive = Environment["INTERACTIVE_SCREENSHOT"] == "true"
val context = injector.getSyncOrNull<OffscreenContext>() ?: OffscreenContext()
val outFile = File("src/jvmTest/screenshots/${context.testClassName.replace(".", "/")}/${context.testMethodName}_$name.png")
val actualBitmap = views.ag.startEndFrame {
//val currentFrameBuffer = views.renderContext.currentFrameBuffer
//Bitmap32(currentFrameBuffer.width, currentFrameBuffer.height).also { ag.readColor(currentFrameBuffer, it) }
view.unsafeRenderToBitmapSync(
views.renderContext,
bgcolor = if (includeBackground) views.clearColor else Colors.TRANSPARENT,
useTexture = useTexture
).depremultiplied().posterizeInplace(posterize)
}
val actualBitmap = simulateRenderFrame(view, posterize, includeBackground, useTexture)

var updateReference = updateTestRef
if (outFile.exists()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,43 +11,55 @@ import korlibs.math.geom.*
import korlibs.render.awt.*
import kotlinx.coroutines.*

fun suspendTestWithOffscreenAG(fboSize: Size, checkGl: Boolean = false, logGl: Boolean = false, callback: suspend CoroutineScope.(ag: AG) -> Unit) = suspendTest {
val fboWidth = fboSize.width.toInt()
val fboHeight = fboSize.height.toInt()
internal fun createKmlGlContext(fboWidth: Int, fboHeight: Int): KmlGlContext {
val ctx = KmlGlContextDefault()
ctx.set()

KmlGlContextDefault().use { ctx ->
//GLOBAL_HEADLESS_KML_CONTEXT.also { ctx ->
val ag = AGOpenglAWT(checkGl = checkGl, logGl = logGl, context = ctx)
ag.mainFrameBuffer.setSize(fboWidth, fboHeight)
ctx.set()
val gl = ag.gl
val gl = ctx.gl

val GL_RGBA8 = 0x8058
val GL_RGBA8 = 0x8058

// Build the texture that will serve as the color attachment for the framebuffer.
val colorRenderbuffer = gl.genRenderbuffer()
gl.bindRenderbuffer(KmlGl.RENDERBUFFER, colorRenderbuffer)
gl.renderbufferStorage(KmlGl.RENDERBUFFER, GL_RGBA8, fboWidth, fboHeight)
gl.bindRenderbuffer(KmlGl.RENDERBUFFER, 0)

// Build the texture that will serve as the color attachment for the framebuffer.
val color_renderbuffer = gl.genRenderbuffer()
gl.bindRenderbuffer(KmlGl.RENDERBUFFER, color_renderbuffer)
gl.renderbufferStorage(KmlGl.RENDERBUFFER, GL_RGBA8, fboWidth, fboHeight)
gl.bindRenderbuffer(KmlGl.RENDERBUFFER, 0)
// Build the texture that will serve as the depth attachment for the framebuffer.
val depthRenderbuffer = gl.genRenderbuffer()
gl.bindRenderbuffer(KmlGl.RENDERBUFFER, depthRenderbuffer)
gl.renderbufferStorage(KmlGl.RENDERBUFFER, KmlGl.DEPTH_COMPONENT, fboWidth, fboHeight)
gl.bindRenderbuffer(KmlGl.RENDERBUFFER, 0)

// Build the texture that will serve as the depth attachment for the framebuffer.
val depth_renderbuffer = gl.genRenderbuffer()
gl.bindRenderbuffer(KmlGl.RENDERBUFFER, depth_renderbuffer)
gl.renderbufferStorage(KmlGl.RENDERBUFFER, KmlGl.DEPTH_COMPONENT, fboWidth, fboHeight)
gl.bindRenderbuffer(KmlGl.RENDERBUFFER, 0)
// Build the framebuffer.
val framebuffer = gl.genFramebuffer()
gl.bindFramebuffer(KmlGl.FRAMEBUFFER, framebuffer)
gl.framebufferRenderbuffer(KmlGl.FRAMEBUFFER, KmlGl.COLOR_ATTACHMENT0, KmlGl.RENDERBUFFER, colorRenderbuffer)
gl.framebufferRenderbuffer(KmlGl.FRAMEBUFFER, KmlGl.DEPTH_ATTACHMENT, KmlGl.RENDERBUFFER, depthRenderbuffer)

// Build the framebuffer.
val framebuffer = gl.genFramebuffer()
gl.bindFramebuffer(KmlGl.FRAMEBUFFER, framebuffer)
gl.framebufferRenderbuffer(KmlGl.FRAMEBUFFER, KmlGl.COLOR_ATTACHMENT0, KmlGl.RENDERBUFFER, color_renderbuffer)
gl.framebufferRenderbuffer(KmlGl.FRAMEBUFFER, KmlGl.DEPTH_ATTACHMENT, KmlGl.RENDERBUFFER, depth_renderbuffer)
val status = gl.checkFramebufferStatus(KmlGl.FRAMEBUFFER)
//if (status != GL_FRAMEBUFFER_COMPLETE)
// Error

return ctx
}

val status = gl.checkFramebufferStatus(KmlGl.FRAMEBUFFER)
//if (status != GL_FRAMEBUFFER_COMPLETE)
// Error
fun suspendTestWithOffscreenAG(fboSize: Size, checkGl: Boolean = false, logGl: Boolean = false, callback: suspend CoroutineScope.(ag: AG) -> Unit) = suspendTest {
val fboWidth = fboSize.width.toInt()
val fboHeight = fboSize.height.toInt()

val ctx = createKmlGlContext(fboWidth, fboHeight)
//GLOBAL_HEADLESS_KML_CONTEXT.also { ctx ->
val ag = AGOpenglAWT(checkGl = checkGl, logGl = logGl, context = ctx)
try {
ag.contextsToFree += ctx
ag.mainFrameBuffer.setSize(fboWidth, fboHeight)
ctx.set()

callback(ag)
} finally {
ag.contextsToFree.forEach { it?.unset(); it?.close() }
ag.contextsToFree.clear()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,38 @@ class KorgeScreenshotTest {
//assertScreenshot(rectContainer, "initial4")
}

@Test
fun testContextLost() = korgeScreenshotTest(
Size(32, 32),
bgcolor = Colors.RED
) {
val rect1 = solidRect(16, 16, Colors.GREEN).position(0, 0)
image(Bitmap32(16, 16, Colors.BLUE).premultipliedIfRequired()).position(16, 0)
assertScreenshot()
simulateContextLost()
assertScreenshot()
}

@Test
fun testContextLost2() = korgeScreenshotTest(
Size(32, 32),
bgcolor = Colors.RED
) {
val rect = solidRect(16, 16, Colors.GREEN).position(0, 0)
//for (n in 0 until 32) solidRect(16, 16, Colors.GREEN.withR(n * 7)).position(n, 0)
//assertScreenshot()
//println("[1]")
simulateRenderFrame()
//println("[2]")
simulateContextLost()
//println("[3]")
val STEPS = 8
for (n in 0 .. STEPS) {
simulateRenderFrame()
rect.x = STEPS - n.toFloat()
}
//println("[4]")
assertScreenshot()
//println("[5]")
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 13 additions & 5 deletions korgw/src/androidMain/kotlin/korlibs/render/KorgwActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,10 @@ import android.os.*
import android.util.*
import android.view.*
import android.view.KeyEvent
import korlibs.datastructure.*
import korlibs.kgl.*
import korlibs.memory.KmemGC
import korlibs.memory.hasBits
import korlibs.graphics.gl.*
import korlibs.event.*
import korlibs.graphics.gl.*
import korlibs.kgl.*
import korlibs.memory.*
import kotlin.coroutines.*

abstract class KorgwActivity(
Expand All @@ -24,6 +22,7 @@ abstract class KorgwActivity(
var mGLView: KorgwSurfaceView? = null
lateinit var ag: AGOpengl
open val agCheck: Boolean get() = false
//open val agCheck: Boolean get() = true
open val agTrace: Boolean get() = false

//init { setOnKeyListener(this) }
Expand All @@ -44,9 +43,18 @@ abstract class KorgwActivity(
}
}

fun Bundle.toMap(): Map<String, Any?> = this.keySet().associateWith { this.get(it) }

public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

intent.extras?.get("sleepBeforeStart")?.toString()?.toLongOrNull()?.let {
Thread.sleep(it)
println("Slept $it milliseconds")
}
//Thread.sleep(100L)

println("intent.extras: ${intent.extras?.toMap()} : ${intent.extras}")
println("---------------- KorgwActivity.onCreate(savedInstanceState=$savedInstanceState) -------------- : ${this.config}")
Log.e("KorgwActivity", "onCreate")
//println("KorgwActivity.onCreate")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ open class KorgwSurfaceView constructor(
val gameWindow: BaseAndroidGameWindow,
val config: GameWindowCreationConfig = gameWindow.config,
) : GLSurfaceView(context), GLSurfaceView.Renderer {
init {
println("!!!!!!!!!!!!! KorgwSurfaceView.created")
}

val view = this

val onDraw = Signal<Unit>()
Expand Down
13 changes: 11 additions & 2 deletions korgw/src/commonMain/kotlin/korlibs/graphics/AGObjects.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ open class AGObject : Closeable {
internal var _cachedVersion: Int = -2
internal var _version: Int = -1

internal fun _resetVersion() {
//_version = RESET_VERSION
markAsDirty()
_cachedVersion = _version - 1
}

protected fun markAsDirty() {
_version++
}
Expand All @@ -30,8 +36,6 @@ open class AGObject : Closeable {
}

class AGBuffer : AGObject() {
internal var lastUploadedSize = 0

var mem: Buffer? = null
private set

Expand All @@ -51,7 +55,12 @@ class AGBuffer : AGObject() {
return this
}

//private val id = LAST_ID.incrementAndGet()
//companion object { private val LAST_ID = KorAtomicInt(0) }
// init { printStackTrace() }

override fun toString(): String = "AGBuffer(${mem?.sizeInBytes ?: 0})"
//override fun toString(): String = "AGBuffer[$id](${mem?.sizeInBytes ?: 0})"
}

data class AGTextureUnits(val textures: Array<AGTexture?>, val infos: AGTextureUnitInfoArray) {
Expand Down
Loading

0 comments on commit 452134d

Please sign in to comment.