Making the code faster

Hi, I'm trying to make a very basic minecraft isometric 10x10x10 world on hte arduino using the TVOut library, but the problem when placing or breaking blocks is that the entire world need to be draw again, and it's really slow because I've made the block sprites in 2 dimensional arrays, and every block in the world is saved in a 3 dimensional array (the position in the array is the y, z and x position, and the number saved in the array is the block type).

I've already tried to make it faster using straight up C code, but it isn't fast enough,

The world drawing was fast, but now the code needs to read every integer on the world array, and that slowed it a lot, but i need the world array to keep track where every block is, because before i was just drawing the blocks with a premade code that cannot be changed during the code execution.

Heres the code:

#include <TVout.h>
#include <fontALL.h>
#include <avr/io.h>
#include <util/delay.h>

TVout TV;

int gb[16][16]={{2,2,2,2,2,2,2,1,1,2,2,2,2,2,2,2}, //Grass Block
                 {2,2,2,2,2,1,1,1,1,1,1,2,2,2,2,2},
                 {2,2,2,1,1,1,1,0,0,1,1,1,1,2,2,2},
                 {2,1,1,1,1,0,0,0,0,0,0,1,1,1,1,2},
                 {1,1,1,0,0,0,0,0,0,0,0,0,0,1,1,1},
                 {1,1,1,1,1,0,0,0,0,0,0,1,1,1,1,1},
                 {1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1},
                 {1,1,0,1,1,1,1,1,1,1,1,1,1,0,1,1},
                 {1,0,0,0,1,1,1,1,1,1,1,1,1,0,0,1},
                 {1,0,0,0,1,0,0,1,1,1,0,0,1,0,0,1},
                 {1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1},
                 {1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1},
                 {2,1,1,0,0,0,0,1,1,0,0,0,0,1,1,2},
                 {2,2,2,1,1,0,0,1,1,0,0,1,1,2,2,2},
                 {2,2,2,2,2,1,1,1,1,1,1,2,2,2,2,2},
                 {2,2,2,2,2,2,2,1,1,2,2,2,2,2,2,2}};

int nb[16][16]={{2,2,2,2,2,2,2,1,1,2,2,2,2,2,2,2},  //Testing Block
                 {2,2,2,2,2,1,1,0,0,1,1,2,2,2,2,2},
                 {2,2,2,1,1,0,0,0,0,0,0,1,1,2,2,2},
                 {2,1,1,0,0,0,0,0,0,0,0,0,0,1,1,2},
                 {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
                 {1,1,1,0,0,0,0,0,0,0,0,0,0,1,1,1},
                 {1,0,0,1,1,0,0,0,0,0,0,1,1,0,0,1},
                 {1,0,0,0,0,1,1,0,0,1,1,0,0,0,0,1},
                 {1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1},
                 {1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1},
                 {1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1},
                 {1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1},
                 {2,1,1,0,0,0,0,1,1,0,0,0,0,1,1,2},
                 {2,2,2,1,1,0,0,1,1,0,0,1,1,2,2,2},
                 {2,2,2,2,2,1,1,1,1,1,1,2,2,2,2,2},
                 {2,2,2,2,2,2,2,1,1,2,2,2,2,2,2,2}};

int sb[16][16]={{2,2,2,2,2,2,2,1,1,2,2,2,2,2,2,2},  //Stone Block
                 {2,2,2,2,2,1,1,0,0,1,1,2,2,2,2,2},
                 {2,2,2,1,1,0,1,1,0,0,0,1,1,2,2,2},
                 {2,1,1,0,0,0,0,0,0,0,1,1,0,1,1,2},
                 {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
                 {1,1,1,0,1,1,0,0,0,0,0,0,0,1,1,1},
                 {1,0,0,1,1,0,0,0,0,0,0,1,1,0,0,1},
                 {1,0,0,0,0,1,1,0,0,1,1,0,0,0,0,1},
                 {1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1},
                 {1,0,0,0,0,0,0,1,1,0,0,0,1,1,0,1},
                 {1,0,0,1,1,0,0,1,1,0,0,0,0,0,0,1},
                 {1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1},
                 {2,1,1,0,0,0,0,1,1,0,0,0,0,1,1,2},
                 {2,2,2,1,1,0,0,1,1,0,0,1,1,2,2,2},
                 {2,2,2,2,2,1,1,1,1,1,1,2,2,2,2,2},
                 {2,2,2,2,2,2,2,1,1,2,2,2,2,2,2,2}};

int bb[16][16]={{2,2,2,2,2,2,2,1,1,2,2,2,2,2,2,2},  //Bedrock Block
                 {2,2,2,2,2,1,1,0,0,1,1,2,2,2,2,2},
                 {2,2,2,1,1,0,0,1,1,0,0,1,1,2,2,2},
                 {2,1,1,0,0,1,1,0,0,1,1,0,0,1,1,2},
                 {1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,1},
                 {1,1,1,0,0,1,1,0,0,1,1,0,0,1,1,1},
                 {1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,1},
                 {1,1,1,0,0,1,1,0,0,1,1,0,0,1,1,1},
                 {1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,1},
                 {1,0,0,0,0,1,1,1,1,1,1,0,0,0,0,1},
                 {1,1,1,0,0,0,0,1,1,0,0,0,0,1,1,1},
                 {1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,1},
                 {2,1,1,0,0,1,1,1,1,1,1,0,0,1,1,2},
                 {2,2,2,1,1,0,0,1,1,0,0,1,1,2,2,2},
                 {2,2,2,2,2,1,1,1,1,1,1,2,2,2,2,2},
                 {2,2,2,2,2,2,2,1,1,2,2,2,2,2,2,2}};

int db[16][16]={{2,2,2,2,2,2,2,1,1,2,2,2,2,2,2,2},  //Dirt Block
                 {2,2,2,2,2,1,1,0,0,1,1,2,2,2,2,2},
                 {2,2,2,1,1,0,0,0,0,0,0,1,1,2,2,2},
                 {2,1,1,0,0,0,0,1,0,0,0,0,0,1,1,2},
                 {1,0,0,0,1,0,1,0,0,0,1,1,0,0,0,1},
                 {1,1,1,0,0,0,0,0,0,1,0,0,0,1,1,1},
                 {1,0,0,1,1,0,0,0,0,0,0,1,1,0,0,1},
                 {1,0,0,0,0,1,1,0,0,1,1,0,0,0,0,1},
                 {1,0,0,0,0,1,0,1,1,0,0,0,0,0,0,1},
                 {1,0,0,1,1,0,0,1,1,0,0,0,0,1,0,1},
                 {1,0,1,0,0,0,0,1,1,0,0,0,1,0,0,1},
                 {1,0,0,0,0,0,0,1,1,0,1,1,0,0,0,1},
                 {2,1,1,0,0,0,0,1,1,0,0,0,0,1,1,2},
                 {2,2,2,1,1,0,0,1,1,0,0,1,1,2,2,2},
                 {2,2,2,2,2,1,1,1,1,1,1,2,2,2,2,2},
                 {2,2,2,2,2,2,2,1,1,2,2,2,2,2,2,2}};

int WD[11][11][11]={0}; //World Array

void drawCube(int t, int x, int y, int z) { //Drawing the cube pixel by pixel
  for(int i=0; i<16; ++i) {
    for(int j=0; j<16; ++j) {
      if(t==1) { //check the block type
        if(gb[j][i] == 1) { //if the block type is a valid one it starts to read every integer from the array (1 if white, 0 if black, 2 if transparent)
          TV.set_pixel((x*8)-(z*8)+i,(x*4)+(z*4)+(y*8)+j,WHITE);
        }
        else if(gb[j][i] == 0) {
          TV.set_pixel((x*8)-(z*8)+i,(x*4)+(z*4)+(y*8)+j,BLACK);
        }
      }
      else if(t==2) {
        if(db[j][i] == 1) {
          TV.set_pixel((x*8)-(z*8)+i,(x*4)+(z*4)+(y*8)+j,WHITE);
        }
        else if(db[j][i] == 0) {
          TV.set_pixel((x*8)-(z*8)+i,(x*4)+(z*4)+(y*8)+j,BLACK);
        }
      }
      else if(t==3) {
        if(sb[j][i] == 1) {
          TV.set_pixel((x*8)-(z*8)+i,(x*4)+(z*4)+(y*8)+j,WHITE);
        }
        else if(sb[j][i] == 0) {
          TV.set_pixel((x*8)-(z*8)+i,(x*4)+(z*4)+(y*8)+j,BLACK);
        }
      }
      else if(t==4) {
        if(bb[j][i] == 1) {
          TV.set_pixel((x*8)-(z*8)+i,(x*4)+(z*4)+(y*8)+j,WHITE);
        }
        else if(bb[j][i] == 0) {
          TV.set_pixel((x*8)-(z*8)+i,(x*4)+(z*4)+(y*8)+j,BLACK);
        }
      }
    }
  }
}

void generateWorld() { //Generating the world
  for(int i=10; i>0; --i) {
    for(int j=0; j<10; ++j) {
      for(int k=0; k<10; ++k) {
        if(i==9){
          WD[i][j][k] = 4;
        }
        else if(i==8){
          WD[i][j][k] = 3;
        }
        else if(i==7){
          WD[i][j][k] = 3;
        }
        else if(i==6){
          WD[i][j][k] = 2;
        }
        else if(i==5){
          WD[i][j][k] = 1;
        }
        else if(i==5){
          WD[i][j][k] = 1;
        }
      }
    }
  }
}

int main() { 
  TV.begin(PAL,170,96);
  generateWorld();
  TV.delay_frame(1);
  while(1) {
    for(int i=10; i>=0; --i) { //Start drawing the world from the bottom, so blocks don't overlap when drawing
      for(int j=0; j<10; ++j) {
        for(int k=0; k<10; ++k) {
          if(WD[i-1][j][k] == 0 or WD[i][j+1][k] == 0 or WD[i][j][k+1] == 0) { //Checking if block is not covered
            if(WD[i][j][k] == 1) {  //If in the world array the block is 1 it's going to draw a grass block on the display
              drawCube(1,k+5,i-3,j-5);
            }
            else if(WD[i][j][k] == 2) {
              drawCube(2,k+5,i-3,j-5);
            }
            else if(WD[i][j][k] == 3) {
              drawCube(3,k+5,i-3,j-5);
            }
            else if(WD[i][j][k] == 4) {
              drawCube(4,k+5,i-3,j-5);
            }
          }
        }
      }
    }
    _delay_ms(1000); //Wait 1 second before redrawing the world (temporary)
    TV.clear_screen(); //Clear the screen to eliminate excess pixels
  }
  return 0;
}```

(I'm italian so sorry for my english)

Thanks!

What happens without this line?

Nothing, it's just there to redraw the world every seconds, i will remove it because i will redraw the world only when blocks are placed or destroyed.

Redraw only when a change has been done.

Yea, but it's time consuming as well.

Do you have to redraw the whole world? Rather than just updating the block that has changed (appeared or disappeared)?

Your code suggests that blocks can be drawn arbitrarily. To remove a block, instead of clearing the whole screen and redrawing it draw an empty block in that spot.

1 Like

What's the current redraw rate? Don't redraw faster then the human eye would catch. 3 - 5 times per second has been a comfortable rate for other screen updates.

Is this a new type of Arduino, or did you mean "the"?

Most Arduino don't have enough memory for the code you posted. Are you using Mega?

I can't picture in my mind how the screen is supposed to look. Can you post some example pictures?

I figured out that your blocks are 16x16 pixels where 1=WHITE, 0=BLACK and 2=transparent (so that pixels behind/under can be seen). Is that correct?

I guess your code is drawing some pixels up to 10 times so that pixels that are above/in front overwrite those behind/below them (unless the pixel above/in front is transparent). This is inefficient, so maybe doing this in a different way would speed up the drawing.

Yes, it's correct, and I have an Arduino MEGA 2560 so thats why I have a lot of memory for this program.
I've already tried to draw only the blocks where at least one of the 3 front faces are visible, but it's slow anyway and i see that redrawing animation i dont want to see.

Redrawing animation:
(https://cpaigin.altervista.org/WIN_20240208_16_46_43_Pro.mp4)

It doesn't work like that, it need to draw the sides of the blocks behind it, and if it redraws them, it will overlay on top of the other blocks.
Just watch this video: https://www.youtube.com/watch?v=Bj9CiMO66xk from 3:50 to 4:05, and you'll know why you can't draw blocks behind others starting from the closest one, because it will overdraw over the front ones.

The redraw rate is 1 fame per seconds, so thats pretty slow, and I think it's because i need to read in arrays and reading in arays is SLOW, I know that pointers are faster, but i don't know how to use them instead of arrays.

Here's some pictures:

Thats supposed to be with all the blocks:

.

Thats supposed to be without one of the upper blocks:

No.

Drawing the pixels is slow.

The Mega is slow, compared to any PC made in the last 20~30 years, and even some calculators.

Mega is even even slower when running TVout library, which consumes a large percentage of its limited computing power.

Is there a metod to make the code faster?

I suggest you use millis() to time exactly how long it takes to update the screen. Then, replace every call to TV.set_pixel() with a call to a dummy function that does nothing. Measure how long it takes to update the screen (nothing will appear, of course, but all the same array reads and calculations will still be done). The difference will tell you that % of the time is taken by TV.set_pixel().

1 Like

A lot of repeating math, so I extracted that from the inner loop,
give it a try

void drawCube(int t, int x, int y, int z) { //Drawing the cube pixel by pixel

  int xx = xx;
  int yy = yy;

  for (int i = 0; i < 16; ++i) 
  {
    for (int j = 0; j < 16; ++j) 
    {
      if (t == 1) { //check the block type
        if (gb[j][i] == 1) { //if the block type is a valid one it starts to read every integer from the array (1 if white, 0 if black, 2 if transparent)
          TV.set_pixel(xx + i, yy + j, WHITE);
        }
        else if (gb[j][i] == 0) {
          TV.set_pixel(xx + i, yy + j, BLACK);
        }
      }
      else if (t == 2) {
        if (db[j][i] == 1) {
          TV.set_pixel(xx + i, yy + j, WHITE);
        }
        else if (db[j][i] == 0) {
          TV.set_pixel(xx + i, yy + j, BLACK);
        }
      }
      else if (t == 3) {
        if (sb[j][i] == 1) {
          TV.set_pixel(xx + i, yy + j, WHITE);
        }
        else if (sb[j][i] == 0) {
          TV.set_pixel(xx + i, yy + j, BLACK);
        }
      }
      else if (t == 4) {
        if (bb[j][i] == 1) {
          TV.set_pixel(xx + i, yy + j, WHITE);
        }
        else if (bb[j][i] == 0) {
          TV.set_pixel(xx + i, yy + j, BLACK);
        }
      }
    }
  }
}

you might win some time by making the type not int but uint8_t.
At least it could save some RAM.

1 Like

It just draw all the blocks in the same spot (upper left), but at least it's drawing them right.

I've already made the blocks arrays in const uint8_t

The TVOut library uses Timer 0 of the microcontroller, and millis() and micro() uses the Timer 0 too, so the returned value is 0 when using the TVOut library because the library is already using that timer.

Ah, I had forgotten that!

No worries. Just print something to serial monitor immediately before and after the screen updates. If you enable the timestamps on serial monitor, you can figure out how many milliseconds it takes. Do it a few times to see how consistent the answer is.

It takes 1.352 second to cicle through the array and writing on the monitor, and it took 1.276 seconds just to cycle through the array, the timings aren't that different because the arduino every times it redraws the world, somethimes it draws it faster, and sometimes slower. I think it's because it's saving some values on the L1 and L2 caches directly.

Ah, I see. Clearly you know more than I do. Good luck.