Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » Shifting Bitmaps

This thread is locked; no one can reply to it. rss feed Print
 1   2 
Shifting Bitmaps
knackname
Member #17,105
August 2019

Is there a way I could redraw a bitmap onto itself, just shifted x pixels in one direction?

I'm working on a system for rendering a hexagonal tile map and I want to render each row of tiles as it's own image. when I move to far to the left I wand all the bitmaps to shift their image 1 tile distance to the right and redraw the now missing tile to the left. I could just allocate a new bitmap and switch them out but is there any way I could avoid that and just copy a bitmaps image to itself but offset?

Chris Katko
Member #1,881
January 2002
avatar

There might be a way to do that but first I have to ask, do you actually NEED to store copies of bitmaps? Why can't you just draw them all to the screen every frame? That kind of stuff was useful in the 80's, not so much today except in the most niche of cases. So if it's for performance reasons (and not some actual geometric requirement to physically create the image you want) I would leave that out completely.

Usually you only keep copies of bitmaps if, during your graphics pipeline, you need intermediate results for later stages. It comes into play with lots of shader stuff.

But as for physically moving a bitmap, the only real way involves getting a much bigger bitmap and only drawing a portion of it like a "viewport" or "window" (allegro calls them sub bitmaps) into the parent bitmap.

You could also try tot use a shader to map old coordinates to new ones (apply a "transformation"). But that'll also use a second copy.

But RAM is cheap, and blitting a texture (even with shading/colors/alpha) that's already in VRAM is practically a zero instruction operation. So again, unless you're doing something incredibly niche and tricky, you're probably over-thinking it.

Just draw all tiles, every frame, with a offset_x and offset_y for the "scrolling" by subtracting offset_x and offset_y from the end coordinates. And don't draw tiles that are completely off the screen.

-----sig:
“Programs should be written for people to read, and only incidentally for machines to execute.” - Structure and Interpretation of Computer Programs
"Political Correctness is fascism disguised as manners" --George Carlin

knackname
Member #17,105
August 2019

It unfortunately is for performance reasons. I tried just drawing the map every frame but the performance was horrible. I already have a buffer bitmap for resizing the display and another buffer bitmap for the map so I can scale it to zoom in and out. I may be doing things completely wrong in that department which could be my issue but with the added strain drawing the map each frame became too much and the program slowed to a crawl.

I'm drawing tiles to a map bitmap that's being scaled and drawn to a buffer bitmap that's being scaled and drawn to the display. The map isn't just a simple one image per tile either, each tile is a range of 1-25 separate images drawn at twice 1080p so I can zoom out to 0.5. And there's around 100-250 individual tiles on screen at a time. It's a quite a lot.

If you have any idea on how I could improve my scaling I'd love to hear them. I had a version of the program written in c working fine before I added the buffers for resizing the display and scaling the map. My current version is in c++. If you want to look over the code I'm using to do it I could send you a file but I don't want to take up too much of your time. For now I think I'll just attempt the copy over to a new bitmap and swap them method. Even if it's not necessary in the long run I don't think it'll hurt anything.

bamccaig
Member #7,536
July 2006
avatar

There are some very smart people around here. Share the code if you can. It's a lot more efficient if you just attach or embed it or link to it or something instead of individual people having to request it. If the code is "proprietary" and you can't/won't share it then I guess do what you have to do.

There's a good chance what you're trying to do isn't unique, and it has been done hundreds of times, and somebody here might already know of a solution to it. Don't worry about taking up anybody's time. These days the message boards are slow around here. The people left sticking around that actually program with (or directly on) Allegro will probably be amused by your problem. If somebody helps it is because they chose to, not because you burdened them with it.

MikiZX
Member #17,092
June 2019

Just a thought... cannot really help in better way as I am completely new to A5, is it possible that one of your destination bitmaps (scaling buffer, the one you draw your scaled graphics to) is a memory bitmap?
I am not sure if this could slow things down a lot but, before your main loop, possibly you can make sure all your bitmaps are video ones (I think the correct command for this would be al_get_bitmap_flags) and eventually if they are memory based convert them to video ones using al_set_new_bitmap_flags() and al_convert_bitmap()?

Not sure if this can help in any way.

knackname
Member #17,105
August 2019

I'll look at it, thanks for the suggestion

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

knackname said:

I'm drawing tiles to a map bitmap that's being scaled and drawn to a buffer bitmap that's being scaled and drawn to the display. The map isn't just a simple one image per tile either, each tile is a range of 1-25 separate images drawn at twice 1080p so I can zoom out to 0.5. And there's around 100-250 individual tiles on screen at a time. It's a quite a lot.

Drawing WIDTH x 2160 x (1-25) * (100-250) is a massive amount of drawing. My first guess would be to use mipmaps but without seeing code and resources I can't guess very well.

Can you make a 7z of your project or is it just too big? Allegr.cc's attachments are limited to 10MB so you might have to upload it somewhere else.

knackname
Member #17,105
August 2019

I tried cutting out the map bitmap and just resizing each tile individually and drawing it to the screen. It means I can't rotate the entire map like I wanted but it has improved performance dramatically.

Now I'm having issues where display events are being delayed. When the map is being drawn it doesn't detect the window being resized or the close button being pressed. It seems like I may be overloading the queue if that's a possibility? Maybe? I don't know.

I uploaded the current version of the project to a google drive, here's a link:

https://drive.google.com/file/d/1bJ7gqKppjJfSjg_nYwgQizA8IJDR5Pu7/view?usp=sharing

the game loop is in SCENE_MANAGER.cpp, the function is mapView

If anyone know how to fix it I'd be grateful. And If anyone sees any other questionable code in there let me know, I'm still pretty knew at this.

The file is in visual studio 2019 but you should be able to take the .h, .cpp and assets folder and open it up in whatever you use

*edit:
I also forgot to reset the tile padding before zipping the file. Its a constant define in MAP_OBJECT.h called tilePadding. line 66. Change the -1 to a 2 to fill the whole screen with tiles

MikiZX
Member #17,092
June 2019

I haven't tried compiling the code though quick-looking at the source I possibly see one thing that appears a bit odd to me (since this is not my source code likely it is my understanding which is off so please be patient).
Namely, in MAP_OBJECT.cpp, lines 157 and 158 would be executed each time the map is drawn - could this be true or is the mapOrientation variable reset somewhere after the initial loading of the bitmaps? If the two commands are executed many times per second this could be a slowdown point as well.

As for the events queue, it is something that I yet have to learn about ...

knackname
Member #17,105
August 2019

I was avoiding storing the tilesheets in memory (I was trying to do what Chris Katko suggested but I probably misinterpreted it) by loading and unloading them each time I needed to redraw the map. I just tried loaded them only when initially creating the map object and It improved dramatically. No issues with the display events not being detected. Thanks a lot MikiZX!

also the mapOrientation is always set to north at the moment. Eventually I'll have it so that the user can change the direction from which the map is drawn

That's fixed but If anyone sees anything odd in the code I'm still all ears. And if anyone knows a way I could still be drawing the tiles to a separate bitmap so I can rotate the whole thing let me know.

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

You can achieve everything you want with transforms alone. I simply draw the same shape at specified offsets over and over and I have no problems. Mind you I don't draw any TILES, only hexagons (filled or outlined). Let me download your code and get back to you.

Ooh, big download...

EDIT
Download complete. Can I take this second to just say VS sucks wad

knackname
Member #17,105
August 2019

It may, but I just started coding last year and VS is what my university uses. What do you recommend?

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

I'm a snob, but I recommend Code::Blocks nightlies and MinGW-W64. But beneath that you should really learn CMake. Because then you could make VS solutions or CB projects from a simple CMakeLists.txt file and CMake.

First, I haven't even looked at any of your files yet, and all you really needed to share was the files contained in this zip. It's 275KB. Your download was 275MB. That's 1000:1 the size it needed to be. This is why you don't learn real coding at a university. But in reality it is only that size because you included nuget binaries for allegro and its deps. That took up the most size. I don't need those.

I'll get back to you once I get it compiling and such.

PHexMapProject.zip

Edgar

knackname
Member #17,105
August 2019

Noted. With that in mind here's the current project after implementing what MikiZX suggested.

https://www.allegro.cc/files/download-attachment/?area=forum%20post&id=1043516

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

knackname
Member #17,105
August 2019

What are the minor mistakes?

You should be able to quit by closing the display. That works on my end. If you hit the close button on the display the displayManager.update() method should set the sceneID to 0 which ends the game loop. For the mapView function it's on lines 331-338 (in SCENE_MANAGER.cpp). And you said I can rotate the whole map image by using transformations? how does that work.

At the beginning of this process the whole thing was a laggy mess but with the suggestions I've already gotten I was able to improve it dramatically already.

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

Here's a diff for you to look at of the minor changes I made.

diff.txt

Note, I am just looking at the code now. Those were the only changes I made to get it to compile without any warnings. Always turn on all warnings.

EDIT1
One thing I will say, is please write a README.txt file that has key controls and screen navigation. I had to guess at everything and quitting using the x button doesn't work on my machine.

EDIT2
You need proper include guards. No more relying on non-portable BS like #pragma. Every header file needs its own set of include guards, with their own unique ID in the define like so :

MySpecialClass.hpp

#ifndef MySpecialClass_HPP
#define MySpecialClass_HPP

/// declarations go here
extern const int ZERO;

class MySpecialClass {
//...
};

#endif // MySpecialClass_HPP

Then you put the definitions in their own source module, as you have been.

EDIT3
Please learn to wrap long lines.

This :

//header
#include "MAP_OBJECT.h"

//construct
MAP_OBJECT::MAP_OBJECT(int seed_input, int size_input, bool biomeToggle_input[], mapTile** tiles_ptr_input, MOUSE_MANAGER* mouseManager_input, KEYBOARD_MANAGER* keyboardManager_input, keyBinding* keybind_ptr)
{

Should be something more like this (most consoles wrap at 80 columns) :

//header
#include "MAP_OBJECT.h"

//construct
MAP_OBJECT::MAP_OBJECT(int seed_input, 
                       int size_input, 
                       bool biomeToggle_input[], 
                       mapTile** tiles_ptr_input,
                       MOUSE_MANAGER* mouseManager_input,
                       KEYBOARD_MANAGER* keyboardManager_input,
                       keyBinding* keybind_ptr)
{

See how much easier that is to read?

EDIT4
Because you have 'z' levels in your hex map, you would have to render it as 3D. Really though, a hexagon is not a difficult shape to replicate. I have a similar hex game, but the hexes are flat on top and bottom instead of the sides. It's in 2D too.

You can achieve everything you want with an extruded 3D hexagon, al_draw_prim and an ALLEGRO_TRANSFORM.

Chris Katko
Member #1,881
January 2002
avatar

I don't have a rig to test this game on ATM, but has anyone thrown it into a profiler and tested where the 90% of cpu time is spent?

-----sig:
“Programs should be written for people to read, and only incidentally for machines to execute.” - Structure and Interpretation of Computer Programs
"Political Correctness is fascism disguised as manners" --George Carlin

MikiZX
Member #17,092
June 2019

I haven't got to actually running the code yet but what Chris suggests made me lookup a short tutorial for Visual Studio regarding the profiling - new knowledge for me. I don't know if this can help knackname:

video

EDIT: Actually I wanted to try the profiling tools and got to trying knackname's code. Though please bear in mind that this is my first ever profiling session and I am not very familiar with Allegro5 internals - I am likely wrong about this. I find that in
DISPLAY_MANAGER::draw()
line 137 : al_flip_display();
seems to be reported as taking some 25 ms on my computer (while line before which is full screen al_draw_scaled_bitmap command only takes some 6ms.
Until now I was to believe that flip_display() would be a command executed quite fast but maybe I am mistaken about flip_display()?

EDIT2: knackname, now that I can see your engine in action I could suggest possible improvement though it might be difficult to implement - you will see. The suggestion is that when it comes to drawing stacked towers of tiles you might actually consider starting your drawing loop of a single tower at a value depending on how high the tower "in front" (one closer to the player) is. So if you have one tower obscuring over half of the tower that is behind it - there is no need to draw the tower that is behind completely - you can draw only the visible part. This should reduce overdraw which might further improve the performance.

Optional (if you have the time and energy):
Eventually you might want to try (as a separate project) to see how VBO work - try drawing a hex tile using VBO (while keeping your Z coordinate in VBO at zero - thus making it 2d). I mention this as using VBOs would reduce the number of function calls between your program and the graphic card thus increasing the graphic performance.

Extra optional:
If you do manage to draw a 2d hex tile using VBO then you are not far off from turning that hex VBO tile into a 3d object which, depending on few choices you make in drawing your map, might be another viable solution.
The main choice which might vote in favor of 3d is that if all height levels (of a single hex tile stack that you have in your map) use the same bitmap(texture) to be drawn which seems to be the case at the moment - then using 3d might make it even faster as you could use less than 10 triangles to draw one tile stack which now, by drawing 25 2d tiles, will end up using some 50 triangles total (actually this reduction in triangles would be applicable to 2d VBO as well provided you only use one-two textures to draw the tile stack).

Though keep in mind that it is easy to be carried away when adding new features to your program so being realistic about the available time and energy you have is always a good call. I think that it is better to complete your program even if it is not completely optimized than to try optimizing everything and ending up with a program that is only half of what you wanted it to be.

knackname
Member #17,105
August 2019

Thanks Edger for the formatting advise, I'll work on implementing that.

With this project I wanted specifically to keep it 2d but I may try 3d at some point in the future. MikiZX what is VBO? What does that stand for?

I'm planning on drawing a sprite for each tile in either 4 or 6 directions and using that as a method to rotate the map. I'll read out of the array backwards or forwards in a dimension to essentially rotate it. I don't know a good way to read out the data from a 2d array to have it match 6 directions though so I'm leaning towards just the 4 at the moment.

I wanted to rotate the map image just for effect so that It spins a little before all the graphics switch orientation and it settles back into place if that makes sense. I'm not looking for a full 3d rotation.

Chris Katko
Member #1,881
January 2002
avatar

knackname said:

MikiZX what is VBO? What does that stand for?

Vertex buffer object. Videocards are fast but the BUS that connects the CPU to them is super slow (relatively speaking) and becomes a huge bottleneck. So all modern cards use batches of operations instead of commanding individual operations. A "display list" is one way, a VBO is another way. A display list is a batch of commands (drawing, coloring, flipping, whatever), and vertex Buffer objects, IIRC, are just an array color, position and texture data.

But before you get to stuff like that that gives you an extra % speedup, make sure you've fixed the core algorithm of what you're doing--because if that's wrong, you're just wasting time optimizing something that's inherently slow because you chose a really slow way to do something.

In 99% of games with 2-D graphics, if it's slow (even in 1080p) you're probably doing something fundamentally wrong. There are TONS of "bullet hell" games that are likely drawing way more than you are and aren't doing any kind of special tricks to accomplish it. (With 4K, the raw screen area does add up quicker, but it's still correct for general cases.)

-----sig:
“Programs should be written for people to read, and only incidentally for machines to execute.” - Structure and Interpretation of Computer Programs
"Political Correctness is fascism disguised as manners" --George Carlin

MikiZX
Member #17,092
June 2019

Yeah, VBO is just an array of points(vertices) where each point can hold information regarding its x,y,z coordinates, color, texture coordinates or more. These points are used to define a triangle (so 3 points are required to define a triangle and the points can be shared between triangles).
Once you can draw a triangle, you can draw a your 2d hex tile by composing 10 triangles together (2 per vertical side and 4 triangles to define the top of the tile).
So ideally you would calculate all the points needed to draw all your tiles, store them in an array and then upload this array to the video card (all this is done only once, before your main loop starts). Following this (and this works both for 3d or 2d), at each video frame your program will draw, you would just tell the video card which triangles to draw and by how much the map needs to be zoomed or translated by.
To do this, in MAP_OBJECT::draw()'s main loop you would not be drawing anything - instead you would be creating index arrays (one per texture used by your tiles) of visible triangles to draw and once this loop is over you would instruct the video card to draw your tiles using these index arrays and al_draw_indexed_prim().
So instead of having possible 2500 calls (100 tiles*25 separate images) to the video card per view, you would end up with some 50-100 calls to the video card per view. The number of draw calls using al_draw_indexed_prim mainly depends on number of different textures you would use to draw your tiles as each al_draw_indexed_prim (single draw call) can only use one texture.
The only thing that might be limiting with VBOs is that you would be drawing textured polygons instead of many bitmaps so your visuals might be different from what you have now (or hope to have).

Anyway, this is an option - if you are doing 2d view then move to VBOs can also be done once your program is complete and working.

If you are even considering VBOs then the best you could do at the moment is to create a test project just to understand and test the VBOs out. There are examples in Allegro5 that cover the use of VBOs and I believe GitHub will have 3rd party example or two - you could start with https://www.allegro.cc/manual/5/al_draw_indexed_prim .
One thing that you might want to try to understand with VBOs is that (and this is to be tested - I am only assuming this is the case) your drawing order might be easily implemented by simply providing a Z value of each point you create whereas now you need to think about the drawing order in your program's logic (drawing from further away tiles to the closer ones, thinking where any eventual object such a tree or a rock will be).

p.s. rotating the view by, for example, 90 degrees would, I believe, require of you to recreate all of the VBOs though this might be fast enough.
Also, creating your objects (tiles) in 3d should make it possible to rotate the view by any degree and at the same time you can use Orthogonal projection to keep your game in 2d plane (but I understand you are trying to avoid 3d - which is OK, I only mention this so you have the info needed to evaluate).

EDIT: At the moment I am trashing your source code trying to move it to VBO and am learning in the process. Actually what I have written above about uploading the VBO only once - it seems I was wrong, the VBO needs to be uploaded each time you wish to draw it.

knackname
Member #17,105
August 2019

I figured out what was happening when Edgar couldn't close the game. When the FPS is too high it takes longer than one frame for the program to draw everything and it skips by the display events in the queue. How do I fix this? I guess I could make a second queue just for the FPS timer separate from the one handling display events?

MikiZX
Member #17,092
June 2019

I cannot really help with events. But as far VBO are concerned I think I got it working (though it will need slight polishing). If you do go about testing this please make sure to have a backup of you actual project before copying these files. You will have to excuse my C++ knowledge which is likely not up to level, as I only know C. If you manage to get it working on your end and if you are interested how it works please let me know your questions.
For me this was a learning experience and I've learned both VBO, transformations and a few other Allegro5 features so a good run altogether.

knackname
Member #17,105
August 2019

I tried completely removing the timer from the event queue and It still doesn't work. The program can't keep up with all the drawing 60 times a second and the longer it runs the more and more the draw calls build up. I'll work on optimizing the draw, but now it looks like I need to be dynamically setting my frame-rate to match how long it takes to draw a frame

Update:
I created another method of closing the game, an on screen button in the upper left, and it works perfectly. Something about all the draws is causing a delay with the display's event output. Still experimenting,
i'll update in a bit.

Update 2:
The results are in, I'm just an idiot. I stopped testing for timer event but I didn't actually remove it as an event source so the timer events were still clogging up the queue. I also put in a system to allow it to skip frames if it hasn't finished drawing the last one.

 1   2 


Go to: