• Dear Cerberus X User!

    As we prepare to transition the forum ownership from Mike to Phil (TripleHead GmbH), we need your explicit consent to transfer your user data in accordance with our amended Terms and Rules in order to be compliant with data protection laws.

    Important: If you accept the amended Terms and Rules, you agree to the transfer of your user data to the future forum owner!

    Please read the new Terms and Rules below, check the box to agree, and click "Accept" to continue enjoying your Cerberus X Forum experience. The deadline for consent is April 5, 2024.

    Do not accept the amended Terms and Rules if you do not wish your personal data to be transferred to the future forum owner!

    Accepting ensures:

    - Continued access to your account with a short break for the actual transfer.

    - Retention of your data under the same terms.

    Without consent:

    - You don't have further access to your forum user account.

    - Your account and personal data will be deleted after April 5, 2024.

    - Public posts remain, but usernames indicating real identity will be anonymized. If you disagree with a fictitious name you have the option to contact us so we can find a name that is acceptable to you.

    We hope to keep you in our community and see you on the forum soon!

    All the best

    Your Cerberus X Team

Android q.

Wingnut

Well-known member
3rd Party Module Dev
Tutorial Author
Joined
Jan 2, 2020
Messages
1,414
I've been working on my game day and night now for the last week or so and getting along with Cerberus really well.
Progress feels great but now I stumped into a problem that causes an immense slowdown on my Android phone so I dedicated the last few nights trying to solve it but no luck. So here I am..

Maybe there are some CX / JAVA experts that knows what needs to be done..
I didn't notice the problem while I was developing on HTML5 and Desktop platforms and and it came as a big surprise
that my Android couldn't handle things quite the same. I know Android can s*ck at things but I was foolish and assumed that this small thing wouldn't be a problem.

I've included a simplified version of the problem below. When I first saw the slowdown my thoughts was that it must be objects being created at runtime.. so I looked around and I managed to throw away most arrays and other things that was buried inside deep loops.

On Android arrays seem to create way more work under the hood than one thinks it should. In a weird way all this got me interested into learning the Android OS because I might need it. About the example code, it might be that you can't barely start. Or it might just work if you got a stronger phone, in that case I urge you too search & replace all 256's and maker them to 512's.

To be able to check if the code is alive I included a moving sprite that moves whenever you touch the screen.
For me it moves very sporadically. If you get it started at all that is. It wil also repeatidly ask the user to kill the game or wait and while it's doing that, you'll see a lot of messages in the console, it basically creates a war inside Android OS.

Am I crazy? All the other platforms manages arrays of 300.000+ integers easily while Android blows a fuze when it reaches just above 10.000 (well in my case).

Screenshot 2020-12-10 at 08.25.19.png




Cerberus:
' For web & desktops, all works perfectly
#HTML5_CANVAS_WIDTH = 1280
#HTML5_CANVAS_HEIGHT = 720
#GLFW_WINDOW_WIDTH = 1280
#GLFW_WINDOW_HEIGHT = 720

' For Android, gives me some problem
#ANDROID_SCREEN_ORIENTATION="landscape"

Import mojo2
Import brl.databuffer

Function Main()
    New MyGame()
End Function

Class MyGame Extends App
    
    Field map:Int[1280*720]  ' CHANGE YOUR RESOLUTION HERE BEFORE RUNNING ON ANDROID
    Field canvas:Canvas
    Field size:Int = 1
    Field mapw:Int = 256
    Field maph:Int = 256
     Field sx:Int = 0
     Field grass:Int = 0
 
    Method OnCreate()
        canvas = New Canvas             
        SetSwapInterval 1 ; SetUpdateRate 0
        
        For Local y = 0 Until maph
            For Local x = 0 Until mapw
                map[x+y*DeviceWidth()] = 2
            Next
        Next

        Return 0
    End Method
 
    Method OnUpdate()
        Local tx:Int = TouchX(0) / size, ty:Int = TouchY(0) / size
        Return 0
    End Method
 
    Method OnRender()
        Local w:Int = DeviceWidth()
        canvas.Clear 0,0,0   
    
        If TouchDown(0) Then sx=sx+4 ; If sx > 1280 Then sx=0
        
        ' ----------------------------------------------------------------------------------------------
        ' This is what I think kills Android, a right loop that counts up 65535 Integers,
        ' 10000-16000 seems to be the maximum that I can run on my Android phone
        ' All other platforms seem able to easily cope on a totally different scale
        '
        For Local y := 0 Until maph Step 1
            For Local x:= 0 Until mapw Step 1
                
                Local col:Int = map[x+y*w] ' ???
                Local r:Int = col Shr 16 & $FF ; Local g:Int = col Shr 8 & $FF ; Local b:Int = col Shr 0 & $FF
                canvas.SetColor Float(r/255.0),Float(g/255.0),Float(b/255.0)
                If col = 0 Then canvas.SetColor 0,0,0
                If col = 2 Then canvas.SetColor 0,1,0
                
                canvas.DrawRect x*size,y*size,size+0,size+0
            Next
        Next
        ' ----------------------------------------------------------------------------------------------
    
        canvas.SetColor 1,1,1
        canvas.DrawRect sx,100,64,64
        canvas.Flush
    End Method
    
End
 
Now measured everything more accurately

* Non-Android platforms can pretty consistency loop through 36.0000-40.000 integers.
* Android can loop through 7.000-10.000 integers.

So around 4 times slower. Maybe there's a possibility to make a difference by shutting down the garbage collection (scary stuff) or dive deeper into Android OS (even more so). But I don't think it's all about size because I did some test using huge arrays with STEP and seem to work fine.

Some valuable insights about the performance that came from all of this; you can always run through an array of 10.000 integers each 60 fps. Anything above that is platform-specific and model-specific.

In my case there's a third option and that would be to translate Autofit into Mojo2 and let it eliminates the need for big arrays.
 
For one thing, running that many drawing operations on Android and in Java, I am not surprised that it is slow. Did you try a drawlist?
 
The slowest part was arrays though, I used a small array to convert color before and it absolutely killed the loop totally, when I took that out and replaced it with simple integer manipulation it became like 90% faster, but many small individual graphic commands is a problem ya true. I measured Android to be 10-16 times slower than iOS.

I converted it to use drawlist ;
Cerberus:
' For web & desktops, all works perfectly
#HTML5_CANVAS_WIDTH = 1280
#HTML5_CANVAS_HEIGHT = 720
#GLFW_WINDOW_WIDTH = 1280
#GLFW_WINDOW_HEIGHT = 720

' For Android, gives me some problem
#ANDROID_SCREEN_ORIENTATION="landscape"

Import mojo2
Import brl.databuffer

Function Main()
    New MyGame()
End Function

Class MyGame Extends App

    Field map:Int[1280*720]  ' CHANGE YOUR RESOLUTION HERE BEFORE RUNNING ON ANDROID
    Field canvas:Canvas

    Field myDrawList:DrawList
 
    Field size:Int = 1
    Field mapw:Int = 256
    Field maph:Int = 256
    Field sx:Int = 0
    Field grass:Int = 0

    Method OnCreate()
        canvas = New Canvas      
        SetSwapInterval 1 ; SetUpdateRate 0
        myDrawList = New DrawList()
        For Local y = 0 Until maph
            For Local x = 0 Until mapw
                map[x+y*DeviceWidth()] = 2
            Next
        Next
 
       Local w:Int = DeviceWidth()
       For Local y := 0 Until maph Step 1
                For Local x:= 0 Until mapw Step 1
                Local col:Int = map[x+y*w] ' ???
                Local r:Int = col Shr 16 & $FF ; Local g:Int = col Shr 8 & $FF ; Local b:Int = col Shr 0 & $FF
                myDrawList.SetColor Float(r/255.0),Float(g/255.0),Float(b/255.0)
                If col = 0 Then myDrawList.SetColor 0,0,0
                If col = 2 Then myDrawList.SetColor 0,1,0
                myDrawList.DrawRect x*size,y*size,size+0,size+0
            Next
        Next
 
        Return 0
    End Method

    Method OnUpdate()
        Local tx:Int = TouchX(0) / size, ty:Int = TouchY(0) / size
        Return 0
    End Method

    Method OnRender()
        Local w:Int = DeviceWidth()
        canvas.Clear 0,0,0

        If TouchDown(0) Then sx=sx+4 ; If sx > 1280 Then sx=0
 
        ' ----------------------------------------------------------------------------------------------

       ' For Local y := 0 Until maph Step 1
       '     For Local x:= 0 Until mapw Step 1
       '  
       '         Local col:Int = map[x+y*w] ' ???
       '         Local r:Int = col Shr 16 & $FF ; Local g:Int = col Shr 8 & $FF ; Local b:Int = col Shr 0 & $FF
       '         canvas.SetColor Float(r/255.0),Float(g/255.0),Float(b/255.0)
       '         If col = 0 Then canvas.SetColor 0,0,0
       '         If col = 2 Then canvas.SetColor 0,1,0
       '  
       '         canvas.DrawRect x*size,y*size,size+0,size+0
       '     Next
       ' Next
        ' ----------------------------------------------------------------------------------------------

       canvas.RenderDrawList(myDrawList, 0,0,0)
           ' canvas.RenderDrawList(myDrawList, MouseX(), MouseY(), Millisecs() * 0.01)
       ' canvas.RenderDrawList(myDrawList, MouseX(), MouseY(), Millisecs() * -0.01)

 
        canvas.SetColor 1,1,1
        canvas.DrawRect sx,100,64,64
        canvas.Flush
    End Method

End

Drawlist wasn't as fast as I thought. You're right It's all in the number of graphiccommands as you say but the fillrate should be very okay so why does one command hinder the speed. because the speed is not great. Maybe I got 50 fps. It's stable though so that's a huge step up but the graphics is well below to what I'm used to.

Android 4.x and up can always fill 4 times of their native resolution and that's the minimum allowed, my old Samsung follows that too. A game engine that I'm working on uses 1000-1500 direct canvas commands and it outputs 60 fps. That's why I focused on the array instead here I guess.

A pattern is starting to show up..
* You have to make sure that you keep array-loops to a maximum of around 8000 items per frame on Android instead of 80.000 like on other platforms.
* Secondly you have to be sure that you keep it a maximum of around 1000-2000 individual graphical commands per frame on Android (instead of 64.000 on other platforms).

If both are followed then you will get really nice performance on Android (in this case a Samsung Note).
Now I don't get it why a single command that fills a quarter of the one screen gives me "just okay" performance, even with empty main loops.

But If it had worked I would still have a problem knowing where and when it would be okay to update the drawing-lists because graphics is supposed to be dynamic. I know that I can move the position and rotate but that is not the kind of dynamics that I need. I need to change the content, maybe every frame. It's not supposed to be a bunch of yellow squares forever.

It seems like dawinglist is slower than direct draw, but it made it perfectly solid there's no messages.
But I can get that same behaviour by keeping the array under 8000 and the number of graphicommands under 1500.
And it will not just be the same but actually perform better.

It's weird because I see drawlist as a batching kind of thing. But it's not behaving like one. It will also perform much worse in HTML and Desktop in all my tests. Of course I've seen the example that follows which is really nice, but as soon as I scale the number of commands, it doesn't perform as good as It should I guess. Not for me anyway.

I guess I'm gonna have to just accept the flaws of Android and work around everything the best I can.
 
Last edited:
Obe day we will have an ndk target for Android. That might speed things up.
 
Have you tried placing 'ontouchdown' in the update instead of on render?

Setupdaterate is set to 0, so it would call OnUpdate as fast as possible - but you are checking for touch on OnRender, which will be called just once in a while when the screen is drawn.
 
Ya actualy I tried to put ontouchDown in both OnRender and OnUpdate and the result is the same sadly. I think it's my GPU that is struggling with remembering big textures in my case. Everything worked until I was forced to glag some textures to be "managed". I threw out anything bigger than 256x256 to make it kinda of work. Still there's a penalty but I get at least 50-60fps that way instead of the 1-4fps I had.

I will probably run into all sorts of problems by doing this but it's still the only viable solution that I can see increase the FPS enough. I could get good speed with any size of textures as long as they weren't flagged as managed but I needed everything to be remembered even if you make the game idle for a while or get a call. So ultimately the touch seems to work allright, I blame the slownes onhow my device reacts on the "managed flag".
 
Btw I was using Monkey2 before this so I did a compare of Cerberus-X relative to M2 and other langauges, and Cerberus-X was around 12ms faster touch at every measurement I made so the speed of touch is a very nice by itself.

I ended up to put it Touch instructions in the OnUpdate as I think that thread should be used for that, but in reality OnRender seem to have access to the variable at the same speed.
 
Well, one thing to remember is that there are A LOT of Android devices that will even fail to load any texture bigger than 1024x1024. Android is like "Windows for mobile", in the sense that it caters to all sorts of devices, with all sorts of hardware.

The only thing I can think off is to reduce texture swapping as much as you can.

Also, why would you need 64k 'graphical commands' on a single frame?! If you're drawing 16x16 images on a 1920x1080 (full HD) screen, that would take around 8k images to fill the entire screen. You want to draw 8x that?!
 
why would you need 64k 'graphical commands' on a single frame?! If you're drawing 16x16 images on a 1920x1080 (full HD) screen, that would take around 8k images to fill the entire screen. You want to draw 8x that?!
Actually that was just a nice surprise while I was learning how modern hardware graphics acceleration works. As I'm finding out what has a high cost and what is essentially free, I changed the design. But the 64 layers was on the desktop platform (Windows and macOS) not Android :)

I have to get back to you when I know more details on Android it's a slow painful process to learn Android now, not having a lot of energy either, so I want to let all that be unsaid.

The texturesize-limit seem to be around 256x256 not 512x512 or 1024x1024, I was hoping to be able to pick 512x512 but too many still needs 256x256. (this will make drawing graphics troublesome I have to split up everything instead of just treating the scene as one single plane).
 
Load command need no managing
The problem I'm having is is say you have a sprite and wanna draw it. Sure if it's loaded it from a png file then you'll get lots of things for free. For once it will essentially be a "ROM" picture, meaning that you don't have to use any expansive managed flag to keep the textures content between context switches and frames. You can rely on it, and it's fast. You never have to consider texture sizes (except 1024+ maybe) You can go HUGE, no expanse.

Dynamic graphics is really expansive sometimes
BUT on the other hand, if you create the same sized image using New image and bind it to a canvas so that you can draw to it and all that... You have the exact same size and content lets say, you you might continue to change this content or not (sometimes you don't want to have png laying around so you create graphics in code, or you just wanna enjoy dynamic graphics) so there's plenty reasons to do this...

Now, you really *have to* set the managed flag to save the content between contexts and frames. Otherwise it will be filled with garbage now and then when you least expect it.

The problem is that the performance becomes terrible now, so you go down in extreme texture sizes.
But doing so only pays part of that huge cost, you're still paying a quiet expansive slowdown. Too expansive than you should I'm sure.

You could do the managing yourself
I'm quiet aware that you can do the managing bit yourself in a much better way than relaying on that flags inner components by using something like 10 opengl commands that re-connects the VBO and any default shaders that mojo2 always use. But I don't have the time to learn how it works exactly so I just go on and use whatever I have, that's why I picked 256x256 becuse I might get away with it.

Mojo1 does not have this problem, but of course it lacks the not only the parts tha Mojo2 handles good but also the parts that Mojo2 handles poorly. Improving the managment of textures will be my top priority if using 256x256 doesn't cut it.
 
I would need you to see the slowdown for yourself but here's the base to understand
It should be possible to reconstruct if you use a slow Android device (or a good one even), and think up a test.

To reach 8000 sprites of 32x32 from a single 256x256 texture per 16msec is a good test.

I loaded my tiles and everything was perfect. When I a bind:ed a canvas to it, it freaked out the device.
But I'm trickling my way out of this one..


Screenshot 2020-12-30 at 04.40.14.png



Cerberus:
Strict
Import mojo2

Function Main:Int()
    New myGame()
    Return 0
End
 ' ------------------------------------------------
Class myGame Extends App

    Field enemyRom : Image
    Field enemyImage : Image
    Field enemyCanvas : Canvas
     Field canvas:Canvas
 ' ------------------------------------------------
    Method OnCreate:Int()
           SetSwapInterval 1 ; SetUpdateRate 0
        canvas = New Canvas() 
            
        ' Load graphics
        enemyRom = Image.Load("enemy.png",0,0,0)
        
        ' Create binded graphicsurface of same size...
        enemyImage = New Image(enemyRom.Width(),enemyRom.Height(),0,0,Image.Managed)
        enemyCanvas = New Canvas(enemyImage)

        ' ... and make the content identical       
        enemyCanvas.Clear
        enemyCanvas.DrawImage enemyRom,0,0 

         Return 0
    End   
 ' ------------------------------------------------
     Method OnRender:Int() 
        canvas.Clear
        
        canvas.DrawImage enemyRom,0,0 ' < Cheap
        
        canvas.DrawImage enemyImage,0,0 ' < Expansive
        
        canvas.Flush
        Return 0
    End
 ' ------------------------------------------------
 End
 
I should add Expensive on *Android* because the other platforms handles it great, the cost is minimal on them (knock on wood)..

EDIT in some cases you can get away with redrawing everything yourself each frame, I've done that as much as I can to get around some problems but it does not cover all situations.

You skip the flag in that case,but you still create the binded surface.
but you move the recreation phase (a simple clear and drawimage in this case) from OnCreate into the OnRender, and basically redraw your sprite every frame. just to get the possiblity to have a dynamic surface to continue to draw on for the rest of the frame. This is an ugly hack but it is very cheap you don't' loose any performance.

Now I can't rely on that anymore though as I need truly dynamic complex content, the whole scene cannot be reconstructed each frame.
 
Last edited:
I am really curious what you are working on. From your description I can't make anything up that would use a solution like you are describing. To me it sounds overkill but I am sure it will make sense when you are presenting it.
 
I have a big landscape ala worms and I invested alot of energy and time (this whole year) to understand how to use the GPU to deform the landscape (that's why I added new blendforms to cerberus, and it works perfectly).

But as I described her I can't keep it alive alive on Android easily, VBO dies likes flies, so the best solution is to go with what I have which is 16 separate 256x256, but I'm not happy about it so I've been tryging solve through other means and jsut about to give up now. So I'm going with 16 256x256 with all the problems that will give me. I will tell later if it worked or not.
 
I can rest asure It really makes sense. Doing it like this wins performance in all the other places and It's kinda of amazing.
 
Here's WIP way to do fast context switch in Mojo2 but I don't know enough to know how to trigger it in Cerberus when context switch happens. It restores and rebinds FBO back as quick as possible.

And this is what have made me wait with the 256x256 solution. Because this would be the perfect solution for Android, it would fix the last piece of the puzzle and make everything superfast.

Cerberus:
Function ReInitVbos:Void()
    glBindBuffer GL_ARRAY_BUFFER,rs_vbo
    glEnableVertexAttribArray 0 ; glVertexAttribPointer 0,2,GL_FLOAT,False,BYTES_PER_VERTEX,0
    glEnableVertexAttribArray 1 ; glVertexAttribPointer 1,2,GL_FLOAT,False,BYTES_PER_VERTEX,8
    glEnableVertexAttribArray 2 ; glVertexAttribPointer 2,2,GL_FLOAT,False,BYTES_PER_VERTEX,16
    glEnableVertexAttribArray 3 ; glVertexAttribPointer 3,4,GL_UNSIGNED_BYTE,True,BYTES_PER_VERTEX,24
    glBindBuffer GL_ELEMENT_ARRAY_BUFFER,rs_ibo
End
Screenshot 2020-12-30 at 13.49.18.png
 
I tried the re-init VBO tonight without success and I noticed that the problem seem to be the regular one; namely a slow Readpixels inside Flush that is triggered whenever the managed flag is set to restore everything. That's expansive of course.

I have a fresh perspective on how to do things without that.
How about a new standard shader to do the actual copying?

This is the gist of it:

[CODE lang="cerberus" title="Shaders"]"You use the texture you rendered to (the one that is used as the FBO color attachment), and sample from it while drawing a screen size quad. You can use very simple shaders for that. The vertex shader for the copy will look something like this:""

attribute vec2 Pos;
varying vec2 TexCoord;
void main() {
TexCoord = 0.5 * Pos + 0.5;
gl_Position = vec4(Pos, 0.0, 1.0);
}

"and the fragment shader:""

uniform sampler2D Tex;
varying vec2 TexCoord;
void main() {
gl_FragColor = texture2D(Tex, TexCoord);
}

"Then you draw a quad that covers the range [-1.0, 1.0] in both x and y"[/CODE]
 
I just realised by reading the Cerberus source, that you don't HAVE to flush unless you update them. I'm not saying i have solved it but I have a feeling there's a chance as I dont' have to update everything every frame.

This examplecode shows 4000 32x32 sprites and 8 256x256 sprites. And I update the 256x256 sprite once every second. That's not too far from unmanaged graphics.

I see perfect 60 fps on an old mobile, so not bad. Of course a solution that makes it possible to restore huge amounts of textures each and every frame (using shader or similar) would be a great idea. But this is well known Android problem, and many people still gets 30fps using Android studio by not being careful enough.

I would really love to have more of everything especially as i'm abit afraid that android will loose on context switch. But I'm thinking of doing some kind of alternate swapping thing.

You don't have to flush managed images unless you've changed them!

So anyways, I'm doing a dynamic version of this, but with destructive landscapes :


spritesheet_draw_anim.gif



Cerberus:
Import mojo2

Function Main()
    New MyApp
End

Class MyApp Extends App

   ' Field gfxCopy2:Image[16]
   ' Field ccanvas2:Canvas[16]
    Field gfxCopy:Image[16]
    Field ccanvas:Canvas[16]
    Field counter:Int
    Field angle:Float
    Field canvas:Canvas
    Field x:Float, y:Float
    Field r:Float
    Field xx:Int=0

    Method OnCreate()  
        canvas = New Canvas()
        canvas.Clear 0,0,0
        counter = 0
        For Local i:Int = 0 To 14
            gfxCopy[i] = New Image(32,32,.0,.0,Image.Managed)
            ccanvas[i] = New Canvas(gfxCopy[i])
             gfxCopy[15] = New Image(256,256,.0,.0,Image.Managed)
            ccanvas[15] = New Canvas(gfxCopy[15])
           
          '  gfxCopy2[i] = New Image(32,32,.0,.0)
          '  ccanvas2[i] = New Canvas(gfxCopy[i])
            ccanvas[i].Clear 0,0,0
             ccanvas[15].Clear 0,0,0
          '  ccanvas2[i].Clear 0,0,0
        Next
        r = 10
        SetSwapInterval 1
        SetUpdateRate 0
    End

    Method OnUpdate()
        angle = angle + 2
    End

    Method OnRender()
        x = Cos(angle) * r + 32 / 2
        y = Sin(angle) * r + 32 / 2
        r = r + 0.1
        xx = xx + 1 ; If xx > 128 Then xx = 0
        If (counter = 0) Or (counter = 7)
            ccanvas[counter].SetColor 0,0,1
            ccanvas[counter].DrawCircle x,y,16
            ccanvas[counter].SetColor 1,0,1
            ccanvas[counter].DrawCircle x,y,30
            ccanvas[counter].Flush
            ccanvas[15].SetColor 0,1,0
            ccanvas[15].DrawCircle 112,112,16
            ccanvas[15].SetColor 1,0,0
            ccanvas[15].Flush
            If counter = 7 Then counter=17 ' stop render to and don't use flush becuase, if we don't update their content then we don't need it
        Endif

        If counter Mod 60 = 0 Then ' update the 256x256 once every second
             ccanvas[15].SetColor Rnd(1),Rnd(1),Rnd(1)
                ccanvas[15].DrawCircle Rnd(255),Rnd(255),40      
              ccanvas[15].Flush
        Endif
       
         canvas.Clear
       
         For Local temp := 0 To 1000 ' 4000 32x32 sprites at 60 fps no problem on old Samsung
       
             canvas.DrawImage gfxCopy[0],xx,0 & 63        'draw 4000 32x32 sprites
             canvas.DrawImage gfxCopy[0],(xx*2) & 127 ,0
              canvas.DrawImage gfxCopy[0],(xx*4) & 255 ,0
              canvas.DrawImage gfxCopy[0],(xx*8) & 511 ,0
         Next
       
          canvas.DrawImage gfxCopy[15],(xx) & 511 ,64 ' draw 8 256x256 sprites on top of that,? YES! 60 fps
          canvas.DrawImage gfxCopy[15],(xx) & 511 ,80
          canvas.DrawImage gfxCopy[15],(xx) & 511 ,90
          canvas.DrawImage gfxCopy[15],(xx) & 511 ,100
          canvas.DrawImage gfxCopy[15],(xx) & 511 ,128
          canvas.DrawImage gfxCopy[15],(xx) & 511 ,140
          canvas.DrawImage gfxCopy[15],(xx) & 511 ,160
          canvas.DrawImage gfxCopy[15],(xx) & 511 ,192
         
         canvas.Flush
         counter = counter + 1
         If counter = 16 Then counter = 0  
    End

End Class
 
You know you have the sources, so no one is stopping you from using your own version of things.
ReadPixels IS slow. Adding to this that it is Java, then of course Android is not the best performant target.
For such a special case like yours I would go either with a custom CX solution or maybe move to tool that fits your needs.
 
Back
Top Bottom