OpenGL canvas if flickering in JFrame on a Windows platform - windows

This question has a "kotlin" label only because it's code is in kotlin, there's nothing kotlin-specific in this example, equivalent java code fails exactly the same way.
Running following code (I've created a repo with this minimal example: https://github.com/shabunc/issues-opengl-flickering):
private fun createGlPanel(): GLCanvas {
val profile = GLProfile.get(GLProfile.GL2)
val capabilities = GLCapabilities(profile)
capabilities.sampleBuffers = true
capabilities.doubleBuffered = true
return GLCanvas(capabilities)
}
private class SwingFrame : JFrame() {
val canvas = createGlPanel()
init {
addWindowListener(object : WindowAdapter() {
override fun windowClosing(evt: WindowEvent) {
dispose()
exitProcess(0)
}
})
contentPane.add(canvas, BorderLayout.CENTER)
setSize(600, 500)
isVisible = false
}
}
fun initApp() {
val frame = SwingFrame()
val canvas = frame.canvas
canvas.addGLEventListener(object : GLEventListener {
override fun reshape(glautodrawable: GLAutoDrawable, x: Int, y: Int, width: Int, height: Int) {
}
override fun init(glautodrawable: GLAutoDrawable) {
glautodrawable.gl.gL2.glClearColor(1f, 0f, 0f, 1f)
val animator = FPSAnimator(glautodrawable, 60, false)
animator.start()
}
override fun dispose(glautodrawable: GLAutoDrawable) {}
override fun display(glautodrawable: GLAutoDrawable) {}
})
frame.isVisible = true
}
fun main() {
val frame = initApp()
}
supposed to launch a window with a red background on it, which is what exactly happens on Linux and MacOS. On windows, though, I got crazy flickering and having hard times to understand how can I change this code to fix this. I would appreciate any hints because hacks like -Dsun.awt.noerasebackground=true (this is only thing I managed to google) ain't working for me as well. I really hope I'm missing something elementary because it's almost "Hello world" level of example.

After investigating some other examples the minimal difference between mine code and code that doesn't flicker was following:
override fun display(glautodrawable: GLAutoDrawable) {
glautodrawable.gl.gL2.glClear(GL2.GL_COLOR_BUFFER_BIT or GL2.GL_DEPTH_BUFFER_BIT)
}

Related

Creating a System Overlay > Can't interact items on screen on Chromebook Android 11

My app creates a system overlay (Always on Top over all Apps).
It works well on Android device version 11, Chromebook device with Android 9.
But It doesn't work on Chromebook Android 11: can't interact items on screen as this video demo
Source code
Following code:
Main configs:
type: WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
Flags: WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
Detail code:
class SampleService : Service() {
lateinit var frameView: View
lateinit var windowManager: WindowManager
override fun onCreate() {
super.onCreate()
windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
setupUi()
}
override fun onBind(p0: Intent?): IBinder? {
return null
}
private fun setupUi() {
listOf(
R.layout.chromeos_view
).forEach { id ->
AsyncLayoutInflater(this).inflate(id, null) { view, resId, _ ->
when (resId) {
R.layout.chromeos_view -> {
frameView = view
windowManager.addView(frameView, getLayoutParams())
}
}
}
}
}
private fun getLayoutParams(): WindowManager.LayoutParams {
val type = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
} else {
WindowManager.LayoutParams.TYPE_PHONE
}
return WindowManager.LayoutParams(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT,
type,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
PixelFormat.TRANSLUCENT
).apply {
gravity = Gravity.START or Gravity.TOP
x = 0
y = 0
alpha = 0.8F
}
}
}
Thank you for any helpful information you can offer, it's very much appreciated!

Handling commands from the viewmodel to the UI

The peculiarity of this application is that every time a user does something (except common things like typing) the application must check with an authority that they are indeed allowed to perform that action.
For example, let us say that the user wishes to see their profile (which is on the top bar)
the Composable screen looks something like this:
#Composable
fun HomeScreen(
navController: NavController,
vm: HomeViewModel = hiltViewModel()
) {
val state = vm.state.value
val scaffoldState = rememberScaffoldState()
HomeScreen(state, scaffoldState, vm::process)
}
#Composable
fun HomeScreen(state: HomeState, scaffoldState: ScaffoldState, event: (HomeEvent) -> Unit) {
Scaffold(
scaffoldState = scaffoldState,
modifier = Modifier.fillMaxSize(),
topBar = {
TopAppBar(
title = {
Text("Hello world")
},
actions = {
IconButton(onClick = {
event.invoke(HomeEvent.ShowProfile)
}) {
Icon(
painter = painterResource(id = R.drawable.ic_person),
contentDescription = stringResource(id = R.string.profile)
)
}
}
)
}
) {
}
}
the view model receives it like so:
#HiltViewModel
class HomeViewModel #Inject constructor(app: Application, private val checkAllowed: CheckAllowed): AndroidViewmodel(app) {
val state = mutableStateOf(HomeState.Idle)
fun process(event:HomeEvent) {
when(event) {
HomeEvent.ShowProfile -> {
state.value = HomeState.Loading
viewModelScope.launch {
try {
val allowed = checkAllowed(Permission.SeeProfile) //use case that checks if the action is allowed
if (allowed) {
} else {
}
} finally {
state.value = HomeState.Idle
}
}
}
}
}
}
I now have to send a command to the ui, to either show a snackbar with the error or navigate to the profile page.
I have read a number of articles saying that compose should have a state, and the correct way to do this is make a new state value, containing the response, and when the HomeScreen receives it , it will act appropriately and send a message back that it is ok
I assume something like this :
in the viewmodel
val command = mutableStateOf<HomeCommand>(HomeCommand.Idle)
fun commandExecuted() {
command.value = HomeCommand.Idle
}
and inside the HomeScreen
val command = vm.command.value
try {
when (command) {
is HomeCommand.ShowProfile -> navController.navigate("profile_screen")
is HomeCommand.ShowSnackbar -> scaffoldState.snackbarHostState.showSnackbar(command.message, "Dismiss", SnackbarDuration.Indefinite)
}
}finally {
vm.commandExecuted()
}
but the way I did it is using flows like so:
inside the viewmodel:
private val _commands = MutableSharedFlow<HomeCommand>(0, 10, BufferOverflow.DROP_LATEST)
val commands: Flow<HomeCommand> = _commands
and inside the HomeScreen:
LaunchedEffect(key1 = vm) {
this#ExecuteCommands.commands.collectLatest { command ->
when (command) {
is HomeCommand.ShowProfile -> navController.navigate("profile_screen")
is HomeCommand.ShowSnackbar -> scaffoldState.snackbarHostState.showSnackbar(command.message, "Dismiss", SnackbarDuration.Indefinite)
}
}
This seems to work, but I am afraid there may be a memory leak or something I'm missing that could cause problems
Is my approach correct? Should I change it to state as in the first example? can I make it better somehow?

My MapsActivity crashes without any error message

I think that the problem is I don't know to use well the Coroutines. In Maps Activity you'll see that I access to a PointsDao suspend function that returns a List of objects that I want to use to create marks at my Google Maps Activity.
#AndroidEntryPoint
class MapsActivity : AppCompatActivity(), OnMapReadyCallback {
private lateinit var mMap: GoogleMap
private lateinit var binding: ActivityMapsBinding
private lateinit var requestPermissionLauncher: ActivityResultLauncher<Array<String>>
private val permissions = arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION)
private lateinit var fusedLocationClient: FusedLocationProviderClient
private val mapsViewModel: MapsViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMapsBinding.inflate(layoutInflater)
setContentView(binding.root)
requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) {
permissions ->
if (permissions.getOrDefault(Manifest.permission.ACCESS_FINE_LOCATION, false)) {
Log.d("fine_location", "Permission granted")
} else {
Log.d("fine_location", "Permission not granted")
getBackToMainActivity()
Toast.makeText(this, "Necessites acceptar els permisos de geolocalització per a realitzar la ruta", Toast.LENGTH_LONG).show()
}
if (permissions.getOrDefault(Manifest.permission.ACCESS_COARSE_LOCATION, false)) {
Log.d("coarse_location", "Permission granted")
} else {
Log.d("coarse_location", "Permission not granted")
getBackToMainActivity()
Toast.makeText(this, "Necessites acceptar els permisos de geolocalització per a realitzar la ruta", Toast.LENGTH_LONG).show()
}
}
// Obtain the SupportMapFragment and get notified when the map is ready to be used.
val mapFragment = supportFragmentManager
.findFragmentById(R.id.map) as SupportMapFragment
mapFragment.getMapAsync(this)
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
requestLocationPermissions()
}
/**
* Manipulates the map once available.
* This callback is triggered when the map is ready to be used.
* This is where we can add markers or lines, add listeners or move the camera.
*/
override fun onMapReady(googleMap: GoogleMap) {
mMap = googleMap
CoroutineScope(Dispatchers.Main).launch {
val listOfPoints = getRoutePoints()
for (point in listOfPoints) {
mMap.addMarker(MarkerOptions().position(LatLng( point.latitude, point.longitude)))
if (point == listOfPoints[0]) {
mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(LatLng(point.latitude, point.longitude), 18f))
}
}
}
}
private fun requestLocationPermissions() {
when (PackageManager.PERMISSION_GRANTED) {
ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) -> {
Log.d("fine_location", "Permission already granted")
}
ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) -> {
Log.d("coarse_location", "Permission already granted")
}
else -> {
requestPermissionLauncher.launch(permissions)
}
}
}
private fun getBackToMainActivity() {
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
}
private fun getRouteId(): Int {
return intent.getIntExtra("routeId", 0)
}
// Gets the points from room repository through ViewModel
private fun getRoutePoints(): List<PointOfInterest> {
val route = getRouteId()
var points = emptyList<PointOfInterest>()
CoroutineScope(Dispatchers.IO).launch {
points = mapsViewModel.getRoutePoints(route)
}
return points
}
This is my ViewModel for this Activity:
#HiltViewModel
class MapsViewModel #Inject constructor(private val repository: RoomRepository): ViewModel() {
suspend fun getRoutePoints(routeId: Int): List<PointOfInterest> {
return repository.getPointsByRouteId(routeId)
}
}
And the Dao:
#Dao
interface PointsDao
{
#Query("SELECT * FROM points_tbl WHERE route_id = :routeId")
suspend fun getRoutePoints(routeId: Int): List<PointOfInterest>
}
My stracktrace error:
Process: com.buigues.ortola.touristics, PID: 27515
java.lang.IllegalStateException: Method addObserver must be called on the main thread
at androidx.lifecycle.LifecycleRegistry.enforceMainThreadIfNeeded(LifecycleRegistry.java:317)
at androidx.lifecycle.LifecycleRegistry.addObserver(LifecycleRegistry.java:172)
at androidx.lifecycle.SavedStateHandleController.attachToLifecycle(SavedStateHandleController.java:49)
at androidx.lifecycle.SavedStateHandleController.create(SavedStateHandleController.java:70)
at androidx.lifecycle.AbstractSavedStateViewModelFactory.create(AbstractSavedStateViewModelFactory.java:67)
at androidx.lifecycle.AbstractSavedStateViewModelFactory.create(AbstractSavedStateViewModelFactory.java:84)
at dagger.hilt.android.internal.lifecycle.HiltViewModelFactory.create(HiltViewModelFactory.java:109)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.kt:171)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.kt:139)
at androidx.lifecycle.ViewModelLazy.getValue(ViewModelLazy.kt:44)
at androidx.lifecycle.ViewModelLazy.getValue(ViewModelLazy.kt:31)
at com.buigues.ortola.touristics.ui.MapsActivity.getMapsViewModel(MapsActivity.kt:39)
at com.buigues.ortola.touristics.ui.MapsActivity.getRoutePoints(MapsActivity.kt:123)
at com.buigues.ortola.touristics.ui.MapsActivity.access$getRoutePoints(MapsActivity.kt:31)
at com.buigues.ortola.touristics.ui.MapsActivity$onMapReady$1.invokeSuspend(MapsActivity.kt:85)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
The problem is here in getRoutePoints().
CoroutineScope(Dispatchers.IO).launch {
points = mapsViewModel.getRoutePoints(route)
}
The by viewModels() in your ViewModel property does a lazy load of the ViewModel. As a result, if you access your ViewModel property for the first time when you are not on the main thread, it will try to create it on the wrong thread, triggering this crash. ViewModels must be constructed on the main thread.
CoroutineScope(Dispatchers.IO) means you are creating a coroutine scope that by default uses background IO threads, so this code is run on a background thread.
You should not be creating a CoroutineScope for this anyway, because your Activity already has one that is properly managed by the Activity lifecycle (so it will cancel any in-progress jobs if the activity is closed, to avoid wasting resources).
Also, getRoutePoints() is a suspend function. There's no reason for you to be using Dispatchers.IO here. A suspend function by convention is safe to call from any dispatcher. (It is however possible to write one that breaks convention, but Room is properly designed and does not break convention.)
To fix the crash and run a coroutine properly, you should use lifecycleScope.launch { //.... However, this function as you have designed it won't do what you expect. It launches a coroutine to retrieve a value, but then it immediately returns before that coroutine has finished running, so in this case will just return the initial emptyList(). When you launch a coroutine, you are queuing up background work, but the current function that called launch continues synchronously without waiting for the coroutine results. If it did, it would be a blocking function. There's more information about that in my answer here.
So, you should instead make this a suspend function:
// Gets the points from room repository through ViewModel
private suspend fun getRoutePoints(): List<PointOfInterest> {
val route = getRouteId()
return mapsViewModel.getRoutePoints(route)
}
And your onMapReady function should also be fixed to use proper scope:
override fun onMapReady(googleMap: GoogleMap) {
mMap = googleMap
lifecycleScope.launch {
val listOfPoints = getRoutePoints()
for (point in listOfPoints) {
mMap.addMarker(MarkerOptions().position(LatLng( point.latitude, point.longitude)))
if (point == listOfPoints[0]) {
mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(LatLng(point.latitude, point.longitude), 18f))
}
}
}
}

How do I make a basic animation on Skiko (Kotlin MPP bindings to Skia)?

Using Skiko and Kotlin I want to make a basic animation: A 0 to 100 counter that automatically updates the text each second.
I managed to do it, but it has a problem, it is blinking each time the window repaints.
Here is the code:
import kotlinx.coroutines.*
import org.jetbrains.skija.*
import org.jetbrains.skiko.*
import javax.swing.*
public fun main() {
val window = SkiaWindow().apply {
layer.renderer = CounterRenderer()
setSize(400, 175)
isVisible = true
defaultCloseOperation = WindowConstants.EXIT_ON_CLOSE
}
GlobalScope.launch {
for (i in 0..100) {
delay(1000)
window.layer.repaint()
}
}
}
public class CounterRenderer : SkiaRenderer {
private lateinit var canvas: Canvas
private var counter = 0
override fun onInit() {
}
override fun onDispose() {
}
override fun onReshape(width: Int, height: Int) {
}
override fun onRender(canvas: Canvas, width: Int, height: Int) {
this.canvas = canvas
val typeface = Typeface.makeFromName("Roboto", FontStyle.NORMAL)
val fontSize = 30F
val font = Font(typeface, fontSize)
val paint = Paint().setColor(0XFF000000.toInt())
canvas.drawString("Counter: ${counter++}", 10F, 50F, font, paint)
}
}
I have tried to search for examples of animations with skija or skiko without success.
I would really appreciate if you could give me some examples.
After navigating the Android Compose code, especially this class:
ComposeLayer.
I finally got it to work with this code:
import kotlinx.coroutines.*
import org.jetbrains.skija.*
import org.jetbrains.skiko.*
import javax.swing.*
public fun main() {
val window = SkiaWindow().apply {
layer.renderer = CounterRenderer()
setSize(400, 175)
isVisible = true
defaultCloseOperation = WindowConstants.EXIT_ON_CLOSE
}
GlobalScope.launch(Dispatchers.Main) {
for (i in 0..100) {
delay(500)
window.layer.redrawLayer()
}
}
}
public class CounterRenderer : SkiaRenderer {
private var counter = 0
private val typeface = Typeface.makeFromName("Roboto", FontStyle.NORMAL)
private val fontSize = 30F
private val font = Font(typeface, fontSize)
private val paint = Paint().setColor(0XFF000000.toInt())
override fun onInit() {
}
override fun onDispose() {
}
override fun onReshape(width: Int, height: Int) {
}
override fun onRender(canvas: Canvas, width: Int, height: Int) {
canvas.drawString("Counter: ${counter++}", 10F, 50F, font, paint)
}
}
To run this code you need to install the specific Main Dispatcher, in this case it is by adding this to the gradle configuration:
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-swing:1.3.9")

How to build a monitor with infinite loop?

I am building a monitor in Kotlin to schedule certain operations, what I want is a program that inserts or updates some database entries for a given time intervall. What I got so far is a program that runs for a given time span, but I have an infinite loop in my porgram that takes up to 30% of processor power when it is not time for an update. So my question is how to build a monitor without an infinite loop?
this my code so far:
while(!operations.done && appConfigurations.run_with_monitor) {
if (DataSourceMonitor.isReadyForUpdate(lastMonitorModel)) {
operations.update()
}
}
operations is an entire sequence of different actions to execute. Each operation implementing the IScheduler interface.
interface IScheduler {
var done: Boolean
fun update()
fun reset()
}
Example of implementation:
class Repeat(private val task: IScheduler) : IScheduler {
override var done = false
override fun update() {
if (this.task.done) {
this.reset()
}
this.task.update()
//logger.info { "Update repeat, done is always $done" }
}
override fun reset() {
this.task.reset()
this.done = false
}
}
class Sequence(private val task1: IScheduler, private val task2: IScheduler): IScheduler {
override var done = false
var current = task1
var next = task2
override fun update() {
if (!this.done) {
this.current.update()
if (this.current.done) {
this.current = this.next
}
if (this.next.done) {
this.done = true
}
}
}
class Print(private val msg: String): IScheduler {
override var done = false
override fun update() {
println(this.msg)
this.done = true
}
override fun reset() {
this.done = false
}
}
The value of operations can be as follows:
val operations = Repeat(Sequence(Print("First action"), Print("Another action")))
**So right now my monitor is working and completely functional, but how can I improve the performance of the infinite loop? **
Hope anyone has some ideas about this.
If your DataSourceMonitor has no way to block until isReadyForUpdate is going to return true, then the usual approach is to add a delay. eg:
while(!operations.done && appConfigurations.run_with_monitor) {
if (DataSourceMonitor.isReadyForUpdate(lastMonitorModel)) {
operations.update()
} else {
Thread.sleep(POLL_DELAY);
}
}
If it's always ready for update there won't be any delay, but if it ever isn't ready for update then it'll sleep. You'll need to tune the POLL_DELAY. Bigger values mean less CPU usage, but greater latency in detecting new events to process. Smaller values produce less latency, but use more CPU.
If you really want to get fancy you can have the poll delay start small and then increase up to some maximum, dropping back down once events are found. This is probably overkill, but look up "adaptive polling" if you're interested.
I have refactored my code and I can accomplish the same result with less code, by removing the IScheduler interface by the abstract class TimerTask. The job can be done with these lines of code:
val scheduler = Sequence(
Print("Executed task 1"),
Sequence(Print("Executed task 2"),
Sequence(Print("Executed task 3"), Print("Finished Scheduler")))
)
Timer().schedule(scheduler, DELAY, PERIOD)
All the interface implementations are changed to TimerTask implementations:
class Print(private val msg: String): TimerTask() {
override fun run() {
println(msg)
}
}
class Sequence(private val task1: Runnable, private val task2: Runnable): TimerTask() {
override fun run() {
task1.run()
task2.run()
}
}

Resources