Low fps with my grid

M

Midastouch

Guest
Hello everyone,
I created a grid (room size). 1024*576
I can fill my grid with water (value in grid = 2)
Or with groud value in grid = 1
My problem is that i have 2 fps no more all the time
Can you help me?

Create event
GML:
grid = ds_grid_create(room_width, room_height);
Step event
Code:
if mouse_check_button(mb_left) {
    ds_grid_set(grid,mouse_x,mouse_y,1)}

if mouse_check_button(mb_right) {
    ds_grid_set(grid,mouse_x,mouse_y,2)}

Draw event i tried 2 ideas 1 with point 1 with sprites

Code:
for(var xx = 0; xx < ds_grid_width(grid); xx++){
    for(var yy = 0; yy < ds_grid_height(grid); yy++){
        if ds_grid_get(grid, xx, yy) = 1 {
            draw_point_color(xx,yy,c_black)
            //draw_sprite(spr_ground,0,xx,yy)
        }
        if ds_grid_get(grid, xx, yy) = 2 {
            draw_point_color(xx,yy,c_blue)
            //draw_sprite(spr_water,0,xx,yy)
        }
    }
}
My draw event is the problem, but i can't find what cause me a low fps like that.
I also havent this problem with a little room.
At 1024 / 576 i have my problem but not at 100/100
 
Last edited by a moderator:

Rob

Member
You've already identified the problem really.

In a 100x100 room you're making 10,000 draw calls a step (60 steps a second usuall) with that double for loop.

In a 1024 X 576 you're making... A lot more!

Are you sure you don't mean to draw things on a 16x16 basis (or something similar) rather than per pixel?
 
M

Midastouch

Guest
You've already identified the problem really.

In a 100x100 room you're making 10,000 draw calls a step (60 steps a second usuall) with that double for loop.

In a 1024 X 576 you're making... A lot more!

Are you sure you don't mean to draw things on a 16x16 basis (or something similar) rather than per pixel?
The aim for me is to create a simple water and sand simulation.
I really want to draw pixel per pixel.
But it is even possible with game maker?

I am inspired by this game : "Noita" (programed with C)
 
Hello o/

You could create a suface in the create event (that you delete in a clean up event).
You would draw on the surface in the step event only one point (to update the surface) and then simply draw the surface once in the draw event.

GML:
surface_set_target(my_surface);
if (mouse_button_pressed(mb_left)) {
    draw_point_color(mouse_x,mouse_y,color);
}
draw_reset_target();
Somthing like that. You would need to code something to select colors in a variable.

In fact that way you don't need the grid, except for exporting the information in text format. A buffer could be nice for that too as it's smaller and faster read.
There are some functions to save surfaces as buffer too.

Edit:
I've seen the game you shown above and it think you should watch about particles in game makers. I loved mataroo's tutorial about the subject :)

Hope it helps o/
 
Last edited:
M

Midastouch

Guest
Hello o/

You could create a suface in the create event (that you delete in a clean up event).
You would draw on the surface in the step event only one point (to update the surface) and then simply draw the surface once in the draw event.

GML:
surface_set_target(my_surface);
if (mouse_button_pressed(mb_left)) {
    draw_point_color(mouse_x,mouse_y,color);
}
draw_reset_target();
Somthing like that. You would need to code something to select colors in a variable.

In fact that way you don't need the grid, except for exporting the information in text format. A buffer could be nice for that too as it's smaller and faster read.
There are some functions to save surfaces as buffer too.

Hope it helps o/
Ok but at the end each pixel will have a density, a velocity.... I am not sure i can do it with buffers maybe a 2d array will be better?
 

Joe Ellis

Member
A buffer is the most efficient way to deal with this, mainly cus of how many pixels are involved. Buffers are 1D, but so are arrays, and you can use a simple math calculation to work out where they should be on the "grid" that you've created with the 1D buffer or array.

GML:
///instance_get_cell_index()

var cell_index = clamp(floor((x - global.min_x) * global.collision_grid_rec), 0, global.cells_x)
+ (global.cells_x * clamp(floor((y - global.min_y) * global.collision_grid_rec), 0, global.cells_y));

return cell_index
global.collision_grid_rec is the reciprocal of global.collision_grid_res (1 / global.collision_grid_res), just so it's a tiny bit faster than dividing.

global.min_x & y is the start position of the grid, which could be negative in my case so it converts the position into 0+ space, it might not be necessary in your case.

This calculation is basically finding out the X position, then for the Y position, it adds the width of the grid * how many Y (rows) it's gone down.
Say if you counted each cell from left to right starting at the top, and at the end of each row you then move down a row and go back to the left of it. That's how grids work in 1D. The precise part is working out the X coordinate, the rough\floored part is working out the Y coordinate. Add the two together and you've got the cell.
 
Last edited:

Joe Ellis

Member
In this case, I know that with this game a special engine was made from scratch in c++ designed for this specific thing, so it can probably handle it a lot better than gm can. But I think if done carefully in gm you could might be able to get away with it.
I think one of the key things is that the grid\buffer is used for collision, while there is also a surface\or graphical element, and the two are basically in sync with each other. Drawing things to the surface is fast to do cus they're done with the gpu, and editing a bunch of cells in the grid is fast to do cus it's editing some bytes with the cpu. Then there's a synchronized system where they're both being changed at the same time that things happen.
This concept, while I've explained how it would work in gm, is actually the same way it would be programmed from scratch in c++, the same principal would apply where it has the two elements (cpu & gpu) working together and synchronized, as it's the only way the computer can manage to do this kind of thing, using the two processors for what they're good at.
 
Last edited:

Simon Gust

Member
Ok but at the end each pixel will have a density, a velocity.... I am not sure i can do it with buffers maybe a 2d array will be better?
It's not the data structure. It's the pure amount of draw-calls that will slow your game down. And that doesn't even include physics and simulation of every pixel yet.
If your grid is 1024 X 576, then you have to make 589824 calls, not including everything happening off-screen, to draw, to simulate and so on. I'm not sure any pc could handle that.
You have to hack your way around these limitations.

The first thing that comes to mind would be to reduce your draw calls by using a surface and only drawing something if it has changed.

I imagine that in Noita, similar to Terraria, they create these datapile from a constructor. This pile of data would represent one pixel, and it could save it's own position, velocity, type and so on. Like an instance, just without all the redundance. With this setup, you can collect these structs in a list and a grid so you can cycle throuh them with the list, and simulate physics on the grid when they're stationary.
This should open up more ways to optimize: For example, you could glue pixels together for a giant blob that only has one simulation step.
 

Joe Ellis

Member
589824 calls of anything, let alone draw calls is probably too much for the computer to handle. You need reduce this to an "as needed" basis, so instances will collide with certain pixels and get the information from them and use it. The grid and graphics should be static and never be processed on the cpu as a whole. Only when needed like I was saying, the instances have access to all this data, and can read from it when needed.
 
Top