Would OpenGL help with this?

I’ve been working on an isometric tile engine written in pure Xojo. It’s a module but the bulk of the work is done by a Canvas subclass.

Currently, the canvas renders large maps sensibly by only drawing visible tiles to the screen but the whole process of painting the tiles (PNG images stored as Pictures) is too slow. There is definitely room for me to optimise which tiles are updated, etc but I’m currently looking at 8 FPS full screen on a 3 yr old MacBook Pro. This is also without any game logic factored in.

I’m wondering if moving away from the Canvas to an OpenGL surface or derivative would help? Seems like overkill to me since I’m only drawing rectangular PNGs to the screen (no polygons or 3D stuff). Also, I know nothing about OpenGL and am wary to rewrite the whole engine if it’s unlikely to improve things.

Does anyone have any thoughts?

OpenGL will be faster, usually way faster. There are some caveats, like anti-aliased lines I find difficult to accomplish.

Here’s a project with a demo drawing tiles…
http://trochoid.weebly.com/uploads/6/2/6/4/62644133/ezgl_1k3.zip 38.7MB
Hopefully this project runs and shows a list of demos, double click on HexTileWindow3 for a 100x100 hex tile grid. Scroll up/down to zoom, drag to move, window resizable. It should be totally smooth.

It should be noted that the 10000 tiles are put in a single ‘display list’ which improves speed. Drawing each individual tile in a for loop does get chunky for me (even OpenGL has it’s limits).

I can explain how to use ezgl when I know what you need. It should be easy if it’s just drawing pictures at position. There’s also X3 http://www.xojo3d.com which has documentation.

It’s also possible your Canvas drawing code can be greatly improved. Composing tiles into larger tile sets might help, or use of InvalidateRect. If you show some of the drawing routine and what/how you need to animate we might have more ideas.

I would suggest there is something not quite right with you code. at one time I wrote a train layout program that used “tiles” and was in multiple layers (tunnels, bridges etc)… and I got near 60FPS out of it…
The trick may be to create an off screen PICTURE object, and only alter that parts of the picture that “change”… and let the PAINT event simply redraw THAT picture instead of doing any calculations there.

Garry, have a look at this thread:

https://forum.xojo.com/15446-x3canvas-with-modelmousedown-event

The X3Canvas is a drop-in replacement for the normal Canvas object, but utilizes OpenGL for rendering. Maybe it could help to give you some ideas on how to apply OpenGL in your project. It is not a full implementation of the Canvas object though.

First up, you’re gonna have to cross over into GPU territory if you want performance.

There’s a couple of ways to do this:

Use OpenGL, you’ll get decent performance across all targeted platforms. However as you’ll be working with low level APIs, there’s a learning curve. Also Apple have not updated their OpenGL libraries as they’re moving to Metal (which has about a 10% performance increase over OpenGL). With OpenGL we can push a full screen 128-Bit floating point image filling a retina MacBook Pro screen over 240 fps. It took months to get it almost right, we still have some weird glitches.

Metal (Mac only), again a low level API with a steep learning curve and is Mac only, you’ll have to use something else for other platforms. I intend to tackle Metal in the not so distant future.

Core Animation Layers (Mac Only), while not as low level as OpenGL or Metal, can be frustrating to initially get to grips with, however they are as they sound. You’d create a layer per tile and the OS will cache that layer to the GPU. Has the added bonus of free animation, simply set the destination and the OS will smoothly animate the transition at 60 GPs for you.

Layer Backed Views (Mac only). Enable layer backed views with one declare and then separate your elements into different canvases, the OS will then cache each canvas onto the GPU for you, used in conjunction with NSView animation declares is probably the easiest way to do some nice smooth animation on the Mac with Xojo. However as the canvases are overlapping it may cause flickering on Windows or Linux.

Lastly:

Core Graphics Layers (Mac Only), I saved these till last as they’re deprecated by Apple now and are not compatible with Layer Backed Views. Basically with CGLayers, you create a bitmap buffer on the GPU and then draw a Xojo picture into that buffer. It’s super fast to then draw that buffer into a canvas. For a game we wrote back in 2006 we used this technique to get 30 fps. They have the advantage that the declares can easily be wrapped in a class so you can then easily add code for Windows and Linux to fall back to Xojo pictures.

Very solid advice; get your calculation code out of the paint event; when it paints it should just paint.

Oh and one more Mac thing, there’s API for drawing tiled images, which is also GPU accelerated. So it’s possible to draw the standard tile covering the canvas, then draw the non-standard tiles on top. There’s an example in the Retina Kit demo application.

A simple OSX solution could be just to use the transformation matrix of Views and layers if your rendering is not too complicated.
And if you’re interested, it shouldn’t be too much work converting SceneKit for OS X/macOS too.

Thanks for all these pointers.

@Alwyn Bester I tried to replace my canvas subclass with your X3Canvas but I didn’t notice any speed improvement at all.

@Sam Rowlands Thanks for the thorough post. I really dislike the idea of using OpenGL in its raw form as I use Xojo for a reason (it’s ease of use). I’m very keen to explore @Will Shank ezgl class although I don’t see much documentation on how to use it.

I’m struggling because on a Retina MacBook with a 64 x 32 pixel tile size that’s 1000-1200 tiles on the screen at once. I thought I had cracked it by drawing the visible tiles to an offscreen buffer picture and updating only the tiles that had changed and then copying the buffer picture to the screen in the Paint event. Seemed to work with a huge increase in FPS (as I was only changing about 8-10 tile per update) but I’ve realised that I’ve goofed because there is a cascade effect by redrawing one tile you need to redraw it’s neighbours and then that tile’s neighbours, etc until you are essentially redrawing the entire screen again.

@Will Shank I will explore the hex demo you have provided. It looks great playing around with it but i shall have to see if I can translate it to an isometric viewpoint.

Hi Garry,

I had a look at the X3Canvas code again, and have to admit that I’ve learned much better ways to deal with 2D graphics in OpenGL since the writing of X3Canvas. I think the problem with the X3Canvas is that it has way too many math in there, which should have been handled directly by the OpenGL projection matrix instead of manually doing calculations and using model objects. (Object instantiation is expensive by nature).

Realizing that you probably want to steer clear of OpenGL to avoid the complexities that comes with low level APIs, I am posting a snippet here anyway of how to set up an OpenGL surface to give pixel perfect projection using orthographic projection (just in case you do want to try out OpenGL at some point for 2D rendering).

OpenGL.glMatrixMode OpenGL.GL_PROJECTION OpenGL.glLoadIdentity OpenGL.glViewport 0, 0, Me.Width, Me.Height OpenGL.glMatrixMode OpenGL.GL_MODELVIEW OpenGL.glLoadIdentity OpenGL.gluOrtho2D(0, Me.Width, Me.Height, 0)

I haven’t tested it, but orthographic projection should be a much faster than rendering 2D graphics the way I did in X3Canvas.

We I’ve been experimenting with the ezgl class. Whilst I don’t understand some of the code that I am blinding copying from Will’s excellent hex map example, I have managed to get a very stripped down version working. I do have a couple of small questions that @Will Shank (or others) may be able to answer:

1). In the attached simple project below, you can click the tiles with either the shift, CTRL, ALT or CMD buttons to place different tiles. You can scroll the map with the arrow keys. It all works as expected except when you CMD-click (to place a small cube) on a tile near the edge of the map. For some reason, that sprite is being drawn with it’s white background. I can’t see how it’s mask is different to any of the other tiles so I don’t know what’s causing that.

2). How would I display a frame rate? I’m curious to see how quick it is. It seems super smooth which is great!

Edit: Here’s the link!

Personally, I tend to go on microseconds; this would include the actual rendering time. Each rendering pass I record the time, but the performance gauge only reads this info every 1/4 of a second.

I’m contemplating writing up how I created the following gauge, because I’m really proud of it.

Here’s a picture of the buggy image:

Here’s the code that assigns the texture for the short and tall cubes:

[code] ’ Cmd-click. Place a cube
if Keyboard.AsyncCommandKey then
map.tiles(tileUnderMouse.column, tileUnderMouse.row).image = _
new EZTexture2D(cube_64_64, cube_64_64.CopyMask, True)
redraw = True
end if

’ Ctrl-click. Place a tall cube
if Keyboard.AsyncControlKey then
map.tiles(tileUnderMouse.column, tileUnderMouse.row).image = _
new EZTexture2D(tall_cube_64_96, tall_cube_64_96.CopyMask, True)
redraw = True
end if[/code]

Here are the two images:

As you can see, the edges around both cubes are white so I don’t think it’s an image problem. Can’t figure out why the top corners of the short cube are rendering white though but the tall one isn’t…

@Sam Rowlands That is pretty nice Sam.

The fix was changing this (in @Will Shank Hex code):

g.texture.enable g.blend.enable g.blend.equation(blendEqn.add) 'g.blend.setFuncStandardAlphaOne 'depends on pixel format

To this:

g.texture.enable g.blend.enable g.blend.equation(blendEqn.add) g.blend.setFuncStandardAlpha

I have no idea what the difference between these two functions is though…

In OpenGL there’s a rendering ‘pipeline’ with a whole bunch of options, switches, and choices along it. To actually draw anything you send in vertices (which can be assiciated with a color, texture coordinate, normal, a few others) and based on the switches the vertices are interpreted as lines or triangles, projected, converted to fragments (pixels) and combined with a buffer.

Blend controls that part where pixels are combined. There’s 2 parts 1) function sets 2 options (factors) that modify the source and destination pixels 2) equation chooses how they are actually combined. And for the machinations of function and equation to be applied blending must be enabled.

There’s 15 choices for both factors of function and I often have to fiddle with them to get it right.

The cube tile was drawing wrong for a difficult to describe reason :slight_smile: For some reason only that cube_64_64 image, when passed to EZTexture2D.Constructor, has white corners in the image portion where alpha is on. In the other pictures the corners are black. Having black corners is what you’d get in a pre-multiplied-with-alpha picture and that’s what setFuncStandardAlphaOne is for (and poorly named). Because the cube isn’t following the pre-multiplied format the function factors mix it wrong. Using setFuncStandardAlpha is for non-pre-multiplied pictures.

I’m not sure why just the cube has white corners. To see what I mean run this code and check Contents of both p in the debugger.

dim p1 As Picture = cube_64_64 //white dim p2 As Picture = tall_cube_64_96 //black break

Also I don’t remember if Pictures are pre-multiplied or not and I haven’t explored the implications of HiDPI on the EZTexture loading routine :frowning: Hmmm, a quick look shows Colors are pulled from an RGBSurface and Color returns unmultiplied values. So setFuncStandardAlpha is right. Yet I know on my mac internally the data is premultiplied so how does white get in there? Anyways I have a couple optimization suggestions I’ll get to later :slight_smile:

To summarize: textures must be enabled for that system to run, blending has to be enabled to support alpha mixing, and the blending function has to be set to match the texture format. The blend equation is Add by default so you don’t have to set that (unless something else has changed it!).

Thanks for the very clear explanation Will.

I’ve made a very simple demo project for the isometric tile engine that manages close to 200 FPS on a Retina display. It’s based on Will’s ezgl class. Here’s the source code.

[quote=275403:@Garry Pettet]Thanks for the very clear explanation Will.

I’ve made a very simple demo project for the isometric tile engine that manages close to 200 FPS on a Retina display. It’s based on Will’s ezgl class. Here’s the source code.[/quote]

Hi to all,
Gary, do you have your isometric démo project to share again ?
I want to adapt my xojo sokoban game (see my github if interested in flat example)

Thanks a lot

Hi Stephen,

I haven’t looked at the code (before today) for years but it still compiles and runs. It’s not complete at all but you can find it here:

https://github.com/gkjpettet/xojo-tile-engine

You might find my GameKit module useful too: https://github.com/gkjpettet/GameKit

Thanks a lot, I will look at it