• 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

Tile sizes for 16:9 displays

Paul59

Active member
CX Code Contributor
Joined
Dec 13, 2018
Messages
384
For anything I've ever made involving tiles, I've stuck to sizes which are multiples of eight, being convinced that performance would be better. Is this still the case with modern CPU/GPUs?

The reason I ask is that 16:9 is now by far the most common display format for desktops/laptops and I'd like to scale up some pixel graphics. The most convenient size for my present project is drawing to a 640x360 canvas and scaling it up to 1280x720 which should be supported for some time yet. Unfortunately it means my tiles will have to be 20x20 - will there be a noticeable performance hit?
 
I can test for you. Will get back soon!
 
Done! It seems that it does not matter one bit. I tried having 20x20 (and 10x10) as the source and destination. Works perfectly

Here is some code so you don't have to take my word for it. It's a bit messy for this but the reason is I just change my own code very quickly to accomate what matters here. It has fat but it should not be terrifying. I

btw I threw in a laser on top and tile animation as it gives the whole thing a more realistic and basically more inspirational test.
It's easy to comment out as well.

Cerberus:
#HTML5_CANVAS_WIDTH=1440'1280'1440
#HTML5_CANVAS_HEIGHT=900'720'900
#ANDROID_SCREEN_ORIENTATION="landscape"
#ANDROID_APP_LABEL="App"
#ANDROID_APP_PACKAGE="com.Cerberus_X.cxgame"
#ANDROID_APP_ICON=""
#ANDROID_SCREEN_ORIENTATION="user"

Import mojo2
Import brl.databuffer

Function Main ()
    New Game
    Return 0
End

Class Game Extends App

    Field vertices:Float[]
    Field indices:Int[]
    Field screen2:Image
    Field screen2canvas:Canvas
    Field intwidth:Float, intheight:Float
    Field startx:Int, starty:Int
    Field fullscreen:Bool = True
    Field canvas:Canvas
    Field icanvas:Canvas
    Field scanvas:Canvas
    Field image:Image
    Field sourceImage:Image
    Field targetImage:Image
    Field size:Int = 10 ' 20 8 ' (Also try 20 as FROM cookiecutting the sprite sheet), All combinations works without degration in speed.
    Field cx:Int
    Field wx:Int
    Field wy:Int
    Field tilemap:Int[512*512]
    Field colourmap:Int[512*512]
    Field cols:Int[]=[$000000, $FFFFFF, $68372B, $70A4B2, $6F3D86, $588D43, $352879, $B8C76F, $6F4F25, $433900, $9A6759, $444444, $6C6C6C, $9AD284, $6C5EB5, $959595]
    Field s:Int = 1'4
    Field ballx:Int = 1
    Field bally:Int = 1
    Field balldx:Int = 2
    Field balldy:Int = 2
    Field oldballx:Int = 0
    Field oldbally:Int = 0
    Field oldballmemory:Int = 0
    Field ballwx:Int = 8
    Field ballwy:Int = 8
    Field ballspeed:Int = 0

    Field scaling:Float=1
    Field pixels:DataBuffer = New DataBuffer(4 * 320 * 200)

    ' Resolutions
    Field vwidth:Int = 320 ' 1280 1366 1920
    Field vheight:Int = 180 ' 200 ' 224 768 800 900 1080
    Field awidth:Int = 1440 ' 1280 768 1280 1366 1920
    Field aheight:Int = 900 ' 800 ' 768 720 800 900 1080

    Field paused:Bool = False
     Field particlex:Int[150]
    Field particley:Int[150]
    Field particlespeed:Int[150]
    Field nbparticle:Int[150]
    Field direction:Int = 0
    Field particle:Image

   Field canvas1:Canvas
   Field layer1:DrawList
   Field canvas2:Canvas
   Field layer2:DrawList

       Field imgBackground:Image
      Field imgSprite:Image
       Field mx:Int, my:Int
  
    Field blend:Int=0

     Field angle:Float
     Field music:Sound
    Field powerup:Sound
    Field music_on:Bool = False

    Field effect:ShaderEffect
    Field level:Float=1   
     Field factor:Int
        
    Method ToggleFullscreen:Void()

    Local desktop:DisplayMode = DesktopMode()
        ' SetDeviceWindow desktop.Width, desktop.Height, 1 ' 4
        awidth = desktop.Width
        aheight = desktop.Height
        fullscreen = Not fullscreen
        If Not fullscreen
            Local desktop:DisplayMode = DesktopMode()
            SetDeviceWindow awidth,aheight, 4+2   
            ShowMouse
            SetSwapInterval 1
            SetUpdateRate 0
        Else
            SetDeviceWindow awidth,aheight, 1    + 2
            HideMouse
            SetSwapInterval 1
            SetUpdateRate 0
        Endif
    End

    Method OnCreate ()
    
'        music = LoadSound("Welcomesong.wav")
'        powerup = LoadSound("Powerup.wav")
  
       canvas1 = New Canvas
       layer1 = New DrawList
       canvas2 = New Canvas
       layer2 = New DrawList
  
        ToggleFullscreen
    
        For Local y:=0 To 511
            For Local x:=0 To 511
                colourmap [x+y*512] = Int(Rnd(15))
                tilemap[x + y * 512] = Int(Rnd(127))
            Next
        Next

        sourceImage=Image.Load("tiles.png",0,0,0)
        targetImage=New Image(sourceImage.Width,sourceImage.Height,,,0)
        image=New Image(128,128,0,0,0)
        Image.SetFlagsMask(Image.Managed)
        icanvas=New Canvas(image)
        scanvas=New Canvas(targetImage)
        ' DW = DeviceWidth()
        ' DH = DeviceHeight()
        canvas=New Canvas
    
        particle=Image.Load("blob.png")
        For Local i:=0 To 149   
            particlex[i] = Rnd(1,320)
            particley[i] = Rnd(1,256)
            particlespeed[i] = Rnd(1,9)
        Next
    
        ' Create screen
        screen2=New Image(vwidth,vheight,0,0,0)
        screen2.SetFlagsMask(screen2.Managed)   
        screen2canvas=New Canvas(screen2)
        oldballx = ballx ; oldbally = bally ; oldballmemory = tilemap[ballx + bally * 512]


        ' ------------------------------------------------------------
        ' FIX FOR NOVEMBER CX-UPDATE (NOT NEEDED OTHERWISE)
        '
        Local www=DeviceWidth, hhh=DeviceHeight   
        canvas.SetViewport 0,0,www,hhh ; canvas.SetScissor 0,0,www,hhh ; canvas.SetProjection2d 0,www,0,hhh
        screen2canvas.SetViewport 0,0,www,hhh ; screen2canvas.SetScissor 0,0,www,hhh ; screen2canvas.SetProjection2d 0,www,0,hhh
        icanvas.SetViewport 0,0,www,hhh ; icanvas.SetScissor 0,0,www,hhh ; icanvas.SetProjection2d 0,www,0,hhh
        scanvas.SetViewport 0,0,www,hhh ; scanvas.SetScissor 0,0,www,hhh ; scanvas.SetProjection2d 0,www,0,hhh
        canvas1.SetViewport 0,0,www,hhh ; canvas1.SetScissor 0,0,www,hhh ;  canvas1.SetProjection2d 0,www,0,hhh
         canvas2.SetViewport 0,0,www,hhh ; canvas2.SetScissor 0,0,www,hhh ; canvas2.SetProjection2d 0,www,0,hhh
        ' ------------------------------------------------------------

        Return 0
    End

    Method OnUpdate ()

        If KeyHit(KEY_ESCAPE) Then OnClose() ' or EndApp or Error ""
    
        If KeyHit( KEY_A )
            #If TARGET = "glfw"
                ToggleFullscreen
            #Endif
        Endif
    
        s = 4
        wx=wx-s*KeyDown(KEY_LEFT)+s*KeyDown(KEY_RIGHT)
        wy=wy-s*KeyDown(KEY_UP)+s*KeyDown(KEY_DOWN)
        wx=Max(0,Min(10000,wx))
        wy=Max(0,Min(10000,wy))
    
           ' If (music_on = False) And (wx > 10) Then music_on = True ; SetChannelVolume 0,1.0 SetChannelPan 0,1 PlaySound music,0
        
            Return 0
    End

    Method OnRender ()

         wx=wx+1
  
        ballspeed = ballspeed + 1

        ' If Texture.TexturesLoading Return
        Local VWIDTH:Int = 320
        Local VHEIGHT:Int = 200
        Local viewW:Int = 1440
        Local viewH:Int = 800
          
          canvas.PushMatrix
        canvas.Scale 1,1
    
        canvas.Clear 0,0,0
        intwidth = Floor(Float(awidth)/Float(vwidth))
         intheight = Floor(Float(aheight)/Float(vheight))
         If intwidth > intheight Then intwidth = intheight
         If intheight > intwidth Then intheight = intwidth
         startx = (awidth-(vwidth*intwidth)) / 2
         starty = (aheight-(vheight*intheight)) / 2
      
           Local borderwidthpercetage:Float = Float(startx)/Float(awidth)
           Local borderheightpercentage:Float = Float(starty)/Float(aheight)
        
         scanvas.Clear
        scanvas.Flush
    
        Local w=vwidth
        Local h=vheight

        screen2canvas.SetViewport 0,0,w,h
        screen2canvas.SetScissor 0,0,w,h
        screen2canvas.SetProjection2d 0,w,0,h
        canvas.Clear 0,0,0
        icanvas.SetViewport 0,0,w,h
        icanvas.SetScissor 0,0,w,h
        icanvas.SetProjection2d 0,w,0,h

        icanvas.Clear 0,0,0
        icanvas.DrawImage sourceImage,0,0
    
        cx=(cx+1) Mod 128
        For Local temp:=0 To 127
            icanvas.DrawRect 8+temp,8,1,8,targetImage,0+temp+cx,8,1,8
        Next

        icanvas.DrawRect 120,8,8,8,targetImage,120,8,8,8
    
        ' Animation test
         For Local temp:=1 To 7
            If Int(Rnd(1)) Then icanvas.SetColor 0,0,0 Else icanvas.SetColor 1,1,1
            Local xx:= Int(Rnd(7))
            Local yy:= Int(Rnd(7))
            icanvas.DrawRect xx*1,yy*1,1*1,1*1
        
         Next
        screen2canvas.Clear 0,0,0' ,0
        screen2canvas.SetColor 1,1,1

        icanvas.Flush

        If wx>4096 Then wx=wx-4096
        ' Draw to canvas
        Local scrx:=wx Mod size ' Cookiecut 32x32 tiles from a 512x512 tilesheet and draw as tiles of any size
        Local scry:=wy Mod size
        Local mapx:=wx / size
        Local mapy:=wy / size
        Local cnty:= -scry

        For Local y:=mapy To mapy+((h/size)+1)
            Local cntx:=-scrx
            For Local x:=mapx To mapx+((w/size)+1)
                Local char:= tilemap[x + y * 512] ; Local tilex:= char & 15 ; Local tiley:= char Shr 4 ' check
            
                Local color:Int = cols[colourmap[x + y * 512]]
                Local r:Int = color Shr 16 & $FF ; Local g:Int = color Shr 8 & $FF ; Local b:Int = color Shr 0 & $FF
                screen2canvas.SetColor Float(r/255.0),Float(g/255.0),Float(b/255.0)
                screen2canvas.DrawRect cntx,cnty,size,size,image,tilex Shl 3,tiley Shl 3,8,8

                cntx=cntx+size
            Next
            cnty=cnty+size
        Next

        screen2canvas.SetColor 1,1,1

        ' Draw ball using world coordinates (WORLD COORDINATE TEST)
        screen2canvas.DrawRect ballwx-wx,ballwy-wy,8*8,8*8,image,0 Shl 3,1 Shl 3,8,8
            ballwx=ballwx+1
            If ballwx > 2000 Then ballwx = 0
        screen2canvas.Flush
        canvas.SetColor 1,1,1   
             canvas.DrawRect startx,starty,vwidth*intwidth,vheight*intheight,screen2,0,0,vwidth,vheight
    
           If borderheightpercentage > 0.01 ' If we want to check percentage of space the border takes of the screen
            ' If starty > 20                  ' If we want to check the number of pixels instead to get the feeling of how much room we've got to spare
            canvas.DrawText("SCORE GOES HERE", awidth / 2.0, (starty-10) / 2, .5)                ' top border
            canvas.DrawText("SCORE GOES HERE", awidth / 2.0, aheight - ((starty+10) / 2), .5) ' bottom border
            Endif

          canvas.PopMatrix
            
       ' LASER
       canvas.SetBlendMode BlendMode.Additive
       canvas.SetAlpha 1.0
        For Local i:=0 To 149
             canvas.SetColor(Float(particlespeed[i]/8.0),Float(particlespeed[i]/8.0),Float(particlespeed[i]/8.0))         
            canvas.DrawImage particle,particlex[i],128+particley[i]/256.0,0.1
            If direction=-1
                particlex[i]=particlex[i]+particlespeed[i]
            Else
                particlex[i]=particlex[i]-particlespeed[i]
            Endif         
            If particlex[i] < 0 particlex[i]= 320
            If particlex[i] > 320 particlex[i]= 0             
        Next

          canvas.Flush
        Return 0
    
    End


End

blob.png
tiles.png
 
Last edited:
The tile.png should be replaced with a tilesheet but with this one it should output this picture (it looks this way because it also tests to use SetColor per tile). Noticed that I use 10x10 here to increase the workload. 20x20 and 10x10 both works as smooth as 8x8.

Screenshot 2020-11-24 at 21.33.18.png
 
Last edited:
Thanks for taking the time to test that. I wonder if drawing the image directly rather than using DrawRect() might make a difference - I'll check that at my end :D
 
I can tell you right away that DrawRect is extremely fast, DrawImage might be as fast or it might be just very close. DrawRect never fails you though.

EDIT
The last piece might be the questionmark if 20x20 also holds up when you add shaders. I'm learning shaders right now and am trying to see if that will degrade anything to this kind of setup, but what I can tell so far it seems like it does not matter. Note that the example has some hardcoded values that I did not change into variables but they don't affect performance I've doublechecked every one of them to be sure.
 
Last edited:
I just created a small program to draw a screen-full of 20x20 and 32x32 tiles ten times over using LoadFrames and DrawImage() - there's no tangible difference between the two in either debug mode or release mode (there is however an interesting spike every so often but that could be down to a multitude of things!).

Are you on Windows BTW?
 
I'm mostly using HTML5 and macos but I regularly test on Android and Windows. This code actually reacts axactly the same on HTML5, Windows, Android, and Macos. Also iOS but I have not tested for awhile on iOS. But the old version was working perfectly.

For HTML5 I use the latest Safari & Chrome on an old Macbook Air from 2012 with last normal (10.15.6) macOS and it outputs desktop performance. I can assure this was not the case back in 2012. Apple have done wonders.

Suprisingly all targets shows the exact same weaknesses and performance, if you make changes they will fail at the same time etc.
It's actually technically easier to see them as ONE. (I've mentioned READPIXELS elsewhere on the forum but that is the only differenc ebetween platforms).

About LoadFrames, I have always avoided LoadFrames and use DrawRect instead. I think that's a wise choice always to do, at least it has been for me. I guess LoadFrames is supposed to make thng easier but I've experience slowdowns.
This can be some misstak from my side though but as DrawRect is everything I need I don't have to think about it.

One totally different thing about graphicsspeed that I forgot to say (good to know if any newcomer sees this) and that is that I've discovered how important it is to to choose the right datatypes. It's too easy to accidentally use slow datatypes (like queues, stacks, or even databuffers etc) inside innerloops, and they can easily fool you that the graphics is slow when it is not. The datatypes themselves can be sluggish. They might be okay for many things but they are not something that you'd would like to have a huge loop of every frame.

Arrays are up for that kind of task. Arrays are great when you want extreme speed. :) :D
 
Last edited:
The worst scenario would be on an old Android phone, and with that as a standing point this example can give you 4 layers if you make the tiles big enough. Big enough means around 48x48 or so. If you are happy with just one layer of tiles it will give you good result with as small as 8x8 tiles. and still you'd have a tiny bit of power left for the actual game before 60fps is broken down.

EDIT : Remember that the above is talking about non-scaled graphics so 48x48 is not bad. This code scales so it allows nice retro graphics on a modern HD / 4K screen and I made sure so this code takes care of the scaling well. I did not use Autofit as i got problems on some devices with ugly scaling. This became a solution for that problem. Anyways, with scaling 20x20 tile would mean 60x60 (on 1366x768) or 80x80 (on 1440x900) etc.

This will give a good picture what the performance are like on different platforms.

On the other platforms like HTML5, Desktop (Windows, macOS) and iOS you can output how many layers you want of any size you want almost. It does not matter, within reason.

I tend to write my game for Android now, I will try to focuse on that for while, and by doing that I know it will run great on all the other platforms.
 
Last edited:
This is actually part a simple indie game engine under 1000 lines that I'm working on now for a Lemmings-inspired game.
So I've tried to put allot of effort the last few months to make it stable on all platforms.

It's a great starting point that can grow without slowing down, It's very scalable.
Windows or macOS doesn't matter at all you'll still get the same results :)
 
Back
Top Bottom