Game development. Programming. Optimization as a stumbling block

Hello everyone! For those who don't know, my name is Sh. Sergey!

Even though I program in Pascal/Assemblerbut I think that for people using other JPthis information may be useful. It is almost impossible to fully consider the issues of program/game optimization, I think it would take a fairly large book to write this, and something will still be missed.

Remember, optimization is not a simple task. The code that they want to optimize can be reviewed dozens of times and get practically no result. And sometimes the slightest changes in the code can give a good result. Therefore, it is advisable to know and understand how to optimize the code and whether it is worth doing this at the moment.

When you couldn't pass by.

I often can't pass by some small moments in the code, and every time I want to find out how well this code works, try to optimize it. And this is my eternal problem.

Going over the code again ZenGL and demo versions in it my gaze accidentally stopped on the sprite engine (8th demo) and, seeing some flaws of the engine I wanted to know how this code works in 32-bit Linuxbecause in the 64-bit system everything worked more or less acceptably. There was only one small moment… I don't have a 32-bit Linux and my adventures began with the fact that I created another virtual machine for myself (VM) Devuan-x86 based on Qemu.

Hidden text

Majority VM which I made are based on Qemu. At the moment, there are about 10 virtual machines, with different OS and different architecture. Including KolibriOS, Debian-ARM32, Debian-ARM64, Windows, Raspberry PI. Sometimes there is too much code to check on different architectures, and rebooting the machine every time for the next VMnot a very good idea.

My main work OS for now Debian 12. I would probably change it to some other distribution, but so much is already configured that I don't have a strong desire to reinstall it. OS once again.

Well, just like I have Linuxthen I can check some of the code for different ones simply by using several terminals. For those who are interested, I described everything a little in one of my articles, go to the topic of debugging, there I described it a little and left links. Perhaps someday I will describe everything related to debugging in more detail.

Don't think that everything is simple enough or that something is too complicated in emulation. Many moments will waste a lot of your time: somewhere on installing programs, somewhere on configuring them, somewhere on testing the code. Therefore, if you do not have a machine set up for developing programs, then you can lose more time setting up the environment than on testing the code or working with the code. After everything is set up, development will go much faster, since you will no longer need to be distracted by this.

Many will not be interested at all VM in any form. Most people need work with a certain OS and that's not wrong. What you should know is that if you decide to make mobile games, then there is no VM almost impossible to do without. Again, in most cases they are already ready for you. Take Android Studio and there are already some of their own there VMwhich only need to be configured. The same is true for the car Makthere are some there VM.

… probably for VM I'll have to make my own separate article… )))

Installed FPC/Lazarusadditional necessary components and launched the demo. The result was not very impressive, 1000 sprites already showed drops. Which you can see at 5:44 (a little further).

And then I finally decided that the code needed to be optimized! And I dove headfirst into it. … only I forgot about some things… these are VMand not a real machine. And a lot could depend on completely different factors, not dependent on the optimization that I did… and a lot depends on the graphics, I don't have a video card forwarded and the result in such cases is deplorable. And I had to use only one processor to see a more correct result.

When I “emerged” from the optimization attempt, almost two weeks had passed. The result was slightly better than in the video. For weak machines there was almost no optimization, and for a more or less powerful machine I got about 17-20 frames per 200,000 objects (sprites). This is about 2 times better than it was, but there is little sense in this, since these objects are not loaded yet, and if there is additional load, everything will simply crash.

Now for your understanding, ZenGL it's mostly a single threaded engine, so 200,000 objects is only on one thread/processor.

But all this is a digression, let's get down to business.

Is optimization necessary?

This is the question you should ask yourself first. You should understand when optimization is needed, and when it will simply take up your time and give almost nothing.

When you are just starting to make a project, you should not think about it too much. At the initial stage, it is quite difficult to create a project that will slow down your machine, or even hang it up. But it is possible. Check that you do not have infinite loops, this is one of the few and most important things that can hang up your program.

When you are doing a small project, it is also very rare to think about optimization. Usually these are “one-day” projects that you do and forget about them. They are needed for you, to test your capabilities.

When you do a big project in a team. Often in such situations, a person or a group of people is allocated who deals with optimization separately. And you personally are needed in this project so that this project gets on its feet faster.

I write and write, and there are more and more facts about the fact that optimization is not necessary… Well, let's add one more fact here: compilers optimize code very strongly now, and what you do manually, such compilers can do in a shorter time and without your participation.

So, should programs be optimized? When should they be optimized?

Yes, optimization is necessary. You alone decided to make a large project, but at some stage the project began to slow down on your computer. Without optimization, your code will only work worse. Therefore, you need to optimize the code, and you need to know how.

Let there be only one example, but it will be important to many (although maybe you are from a group where you are engaged in optimization in your project? Here is a second example.).

Algorithms.

This is one of the important points of optimization and many of you have studied various algorithms, and most have already encountered various algorithms, quite possibly without even knowing about it.

Want to learn more about algorithms? Check out this popular book: Donald Knuth “The Art of Computer Programming”volume one. This might be what you need.

Algorithms are important for optimizing your program, using certain algorithms, you can optimize the code quite well. But you should also know that many algorithms are already in the code and are used. It is quite possible that you should learn more about your JP and what algorithms does it provide in its libraries?

It is desirable to know algorithms, but this is not a panacea. Perhaps your understanding is enough to optimize your program. And perhaps it is precisely the algorithms that will help you with this. There are a lot of different algorithms and you clearly do not need all of them.

Let me add a little bit about algorithms. The point is that there are algorithms that are either little described or little used. Or maybe there is another reason? You can consider any of your optimizations as an algorithm, thanks to which your code began to work better and faster (the most interesting thing may turn out that the algorithm you use was created a long time ago, you just don’t know about it, but just use it). In fact, if you can apply a similar action to other programs, then this is already an algorithm. )))

But for most, the algorithms will be those that are already installed everywhere.

Variables, constants, arrays, structures.

With constants, you can set some frequently used values ​​to use in your code (your compiler probably does this for you by default). You take this constant and insert it without calculations into the code where you previously performed calculations, but as it turns out, this was not necessary.

Is it necessary if you already have a powerful compiler? I think it is, large programs use thousands (thousands of thousands?) of constants. They at least reduce the compilation time of your program, which is already good if your program is large.

Variables, arrays and structures (classes can also be included here) also allow you to calculate certain data for the working code. But here I will make a reservation, I am talking specifically about optimization! Very often, there is interrelated code in different modules (different files) and you may not pay attention to such things. But if you did, then you can optimize just such a moment.

What did I want to tell you with this? Let's look at this with a code example Pascal.

Hidden text
unit zgl_font;

{$I zgl_config.cfg}

interface
uses
  zgl_textures,
  zgl_math_2d,
  zgl_file,
  zgl_memory,
  zgl_types;

const
  ZGL_FONT_INFO : array[ 0..13 ] of AnsiChar = ( 'Z', 'G', 'L', '_', 'F', 'O', 'N', 'T', '_', 'I', 'N', 'F', 'O', #0 );
type
  zglPCharDesc = ^zglTCharDesc;
  zglTCharDesc = record
    Page      : Word;
    Width     : Byte;
    Height    : Byte;
    ShiftX    : Integer;
    ShiftY    : Integer;
    ShiftP    : Integer;
    TexCoords : array[ 0..3 ] of zglTPoint2D;
  end;

  zglPFont = ^zglTFont;
  zglTFont = record
    Count      : record
      Pages : Word;
      Chars : Word;
                 end;

    Pages      : array of zglPTexture;
    CharDesc   : array[ 0..65535 ] of zglPCharDesc;
    MaxHeight  : Integer;
    MaxShiftY  : Integer;
    Padding    : array[ 0..3 ] of Byte;

    prev, next : zglPFont;
  end;

  zglPFontManager = ^zglTFontManager;
  zglTFontManager = record
    Count : Integer;
    First : zglTFont;
  end;

procedure font_Load( var fnt : zglPFont; var fntMem : zglTMemory );

var
  managerFont : zglTFontManager;

implementation 
//...
procedure font_Load( var fnt : zglPFont; var fntMem : zglTMemory );
  var
    i     : Integer;
    c     : LongWord;
    fntID : array[ 0..13 ] of AnsiChar;
begin
  fntID[ 13 ] := #0;
  mem_Read( fntMem, fntID, 13 );
  if fntID <> ZGL_FONT_INFO Then
    begin
      if Assigned( fnt ) Then
        FreeMemory( fnt );
      fnt := nil;
      exit;
    end;

  if not Assigned( fnt ) Then
    fnt := font_Add();
  mem_Read( fntMem, fnt.Count.Pages,  2 );
  mem_Read( fntMem, fnt.Count.Chars,  2 );
  mem_Read( fntMem, fnt.MaxHeight,    4 );
  mem_Read( fntMem, fnt.MaxShiftY,    4 );
  mem_Read( fntMem, fnt.Padding[ 0 ], 4 );
  SetLength( fnt.Pages, fnt.Count.Pages );
  for i := 0 to fnt.Count.Pages - 1 do
    fnt.Pages[ i ] := nil;
  for i := 0 to fnt.Count.Chars - 1 do
    begin
      mem_Read( fntMem, c, 4 );
      zgl_GetMem( Pointer( fnt.CharDesc[ c ] ), SizeOf( zglTCharDesc ) );
      {$IFDEF ENDIAN_BIG}
      forceNoSwap := TRUE;
      {$ENDIF}
      mem_Read( fntMem, fnt.CharDesc[ c ].Page, 4 );
      {$IFDEF ENDIAN_BIG}
      forceNoSwap := FALSE;
      {$ENDIF}
      mem_Read( fntMem, fnt.CharDesc[ c ].Width, 1 );
      mem_Read( fntMem, fnt.CharDesc[ c ].Height, 1 );
      mem_Read( fntMem, fnt.CharDesc[ c ].ShiftX, 4 );
      mem_Read( fntMem, fnt.CharDesc[ c ].ShiftY, 4 );
      mem_Read( fntMem, fnt.CharDesc[ c ].ShiftP, 4 );
      {$IFDEF ENDIAN_BIG}
      mem_Read( fntMem, fnt.CharDesc[ c ].TexCoords[ 0 ].X, 4 );
      mem_Read( fntMem, fnt.CharDesc[ c ].TexCoords[ 0 ].Y, 4 );
      mem_Read( fntMem, fnt.CharDesc[ c ].TexCoords[ 1 ].X, 4 );
      mem_Read( fntMem, fnt.CharDesc[ c ].TexCoords[ 1 ].Y, 4 );
      mem_Read( fntMem, fnt.CharDesc[ c ].TexCoords[ 2 ].X, 4 );
      mem_Read( fntMem, fnt.CharDesc[ c ].TexCoords[ 2 ].Y, 4 );
      mem_Read( fntMem, fnt.CharDesc[ c ].TexCoords[ 3 ].X, 4 );
      mem_Read( fntMem, fnt.CharDesc[ c ].TexCoords[ 3 ].Y, 4 );
      {$ELSE}
      mem_Read( fntMem, fnt.CharDesc[ c ].TexCoords[ 0 ], SizeOf( zglTPoint2D ) * 4 );
      {$ENDIF}
    end;
end;
unit zgl_text;

{$I zgl_config.cfg}

interface
uses
  zgl_font,
  zgl_math_2d,
  zgl_types;

const
  TEXT_HALIGN_LEFT    = $000001;
  TEXT_HALIGN_CENTER  = $000002;
  TEXT_HALIGN_RIGHT   = $000004;
  TEXT_HALIGN_JUSTIFY = $000008;
  TEXT_VALIGN_TOP     = $000010;
  TEXT_VALIGN_CENTER  = $000020;
  TEXT_VALIGN_BOTTOM  = $000040;
  TEXT_CLIP_RECT      = $000080;
  TEXT_FX_VCA         = $000100;
  TEXT_FX_LENGTH      = $000200;

procedure text_Draw( Font : zglPFont; X, Y : Single; const Text : UTF8String; Flags : LongWord = 0 );
//...
implementation 
//...
procedure text_Draw( Font : zglPFont; X, Y : Single; const Text : UTF8String; Flags : LongWord = 0 );
  var
    i, c, s  : Integer;
    charDesc : zglPCharDesc;
    quad     : array[ 0..3 ] of zglTPoint2D;
    sx       : Single;
    lastPage : Integer;
    mode     : Integer;
begin
  if ( Text="" ) or ( not Assigned( Font ) ) Then exit;
  for i := 0 to Font.Count.Pages - 1 do
    if not Assigned( Font.Pages[ i ] ) Then exit;

  glColor4ubv( @textRGBA[ 0 ] );

  Y := Y - Font.MaxShiftY * textScale;
  if Flags and TEXT_HALIGN_CENTER > 0 Then
    X := X - Round( text_GetWidth( Font, Text, textStep ) / 2 ) * textScale
  else
    if Flags and TEXT_HALIGN_RIGHT > 0 Then
      X := X - Round( text_GetWidth( Font, Text, textStep ) ) * textScale;
  sx := X;

  if Flags and TEXT_VALIGN_CENTER > 0 Then
    Y := Y - ( Font.MaxHeight div 2 ) * textScale
  else
    if Flags and TEXT_VALIGN_BOTTOM > 0 Then
      Y := Y - Font.MaxHeight * textScale;

  FillChar( quad[ 0 ], SizeOf( zglTPoint2D ) * 4, 0 );
  charDesc := nil;
  lastPage := -1;
  c := utf8_GetID( Text, 1, @i );
  s := 1;
  i := 1;
  if Flags and TEXT_FX_VCA > 0 Then
    mode := GL_TRIANGLES
  else
    mode := GL_QUADS;
  if not b2dStarted Then
    begin
      if Assigned( Font.CharDesc[ c ] ) Then
        begin
          lastPage := Font.CharDesc[ c ].Page;
          batch2d_Check( mode, FX_BLEND, Font.Pages[ Font.CharDesc[ c ].Page ] );

          glEnable( GL_BLEND );
          glEnable( GL_TEXTURE_2D );
          glBindTexture( GL_TEXTURE_2D, Font.Pages[ Font.CharDesc[ c ].Page ].ID );
          glBegin( mode );
        end else
          begin
            glEnable( GL_BLEND );
            glEnable( GL_TEXTURE_2D );
            glBegin( mode );
          end;
    end;
  while i <= Length( Text ) do
    begin
      if Text[ i ] = #10 Then
        begin
          X := sx;
          Y := Y + Font.MaxHeight * textScale;
        end;
      c := utf8_GetID( Text, i, @i );

      if ( Flags and TEXT_FX_LENGTH > 0 ) and ( s > textLength ) Then
        begin
          if s > 1 Then
            begin
              if Assigned( textLCoord ) Then
                begin
                  textLCoord.X := quad[ 0 ].X + Font.Padding[ 0 ] * textScale;
                  textLCoord.Y := quad[ 0 ].Y + Font.Padding[ 1 ] * textScale;
                end;
              if Assigned( textLCharDesc ) Then
                textLCharDesc^ := charDesc^;
            end;
          break;
        end;
      INC( s );

      charDesc := Font.CharDesc[ c ];
      if not Assigned( charDesc ) Then continue;

      if lastPage <> charDesc.Page Then
        begin
          lastPage := charDesc.Page;

          if ( not b2dStarted ) Then
            begin
              glEnd();

              glBindTexture( GL_TEXTURE_2D, Font.Pages[ charDesc.Page ].ID );
              glBegin( mode );
            end else
              if batch2d_Check( mode, FX_BLEND, Font.Pages[ charDesc.Page ] ) Then
                begin
                  glEnable( GL_BLEND );

                  glEnable( GL_TEXTURE_2D );
                  glBindTexture( GL_TEXTURE_2D, Font.Pages[ charDesc.Page ].ID );
                  glBegin( mode );
                end;
        end;

      quad[ 0 ].X := X + ( charDesc.ShiftX - Font.Padding[ 0 ] ) * textScale;
      quad[ 0 ].Y := Y + ( charDesc.ShiftY + ( Font.MaxHeight - charDesc.Height ) - Font.Padding[ 1 ] ) * textScale;
      quad[ 1 ].X := X + ( charDesc.ShiftX + charDesc.Width + Font.Padding[ 2 ] ) * textScale;
      quad[ 1 ].Y := Y + ( charDesc.ShiftY + ( Font.MaxHeight - charDesc.Height ) - Font.Padding[ 1 ] ) * textScale;
      quad[ 2 ].X := X + ( charDesc.ShiftX + charDesc.Width + Font.Padding[ 2 ] ) * textScale;
      quad[ 2 ].Y := Y + ( charDesc.ShiftY + charDesc.Height + ( Font.MaxHeight - charDesc.Height ) + Font.Padding[ 3 ] ) * textScale;
      quad[ 3 ].X := X + ( charDesc.ShiftX - Font.Padding[ 0 ] ) * textScale;
      quad[ 3 ].Y := Y + ( charDesc.ShiftY + charDesc.Height + ( Font.MaxHeight - charDesc.Height ) + Font.Padding[ 3 ] ) * textScale;

      if Flags and TEXT_FX_VCA > 0 Then
        begin
          glColor4ubv( @fx2dVCA1[ 0 ] );
          glTexCoord2fv( @charDesc.TexCoords[ 0 ] );
          glVertex2fv( @quad[ 0 ] );

          glColor4ubv( @fx2dVCA2[ 0 ] );
          glTexCoord2fv( @charDesc.TexCoords[ 1 ] );
          glVertex2fv( @quad[ 1 ] );

          glColor4ubv( @fx2dVCA3[ 0 ] );
          glTexCoord2fv( @charDesc.TexCoords[ 2 ] );
          glVertex2fv( @quad[ 2 ] );

          glColor4ubv( @fx2dVCA3[ 0 ] );
          glTexCoord2fv( @charDesc.TexCoords[ 2 ] );
          glVertex2fv( @quad[ 2 ] );

          glColor4ubv( @fx2dVCA4[ 0 ] );
          glTexCoord2fv( @charDesc.TexCoords[ 3 ] );
          glVertex2fv( @quad[ 3 ] );

          glColor4ubv( @fx2dVCA1[ 0 ] );
          glTexCoord2fv( @charDesc.TexCoords[ 0 ] );
          glVertex2fv( @quad[ 0 ] );
        end else
          begin
            glTexCoord2fv( @charDesc.TexCoords[ 0 ] );
            glVertex2fv( @quad[ 0 ] );

            glTexCoord2fv( @charDesc.TexCoords[ 1 ] );
            glVertex2fv( @quad[ 1 ] );

            glTexCoord2fv( @charDesc.TexCoords[ 2 ] );
            glVertex2fv( @quad[ 2 ] );

            glTexCoord2fv( @charDesc.TexCoords[ 3 ] );
            glVertex2fv( @quad[ 3 ] );
          end;

      X := X + ( charDesc.ShiftP + textStep ) * textScale;
    end;

  if not b2dStarted Then
    begin
      glEnd();

      glDisable( GL_TEXTURE_2D );
      glDisable( GL_BLEND );
    end;
end;
//...

Source code Here.

Pay attention to the second module zgl_text. This module uses the data received from the first module and constantly calculates it. If the module zgl_font is used only when loading the application, then zgl_text is used constantly, where it is necessary to display text. And the code given below will work every time it is necessary to draw the next symbol.

quad[ 0 ].X := X + ( charDesc.ShiftX - Font.Padding[ 0 ] ) * textScale;
quad[ 0 ].Y := Y + ( charDesc.ShiftY + ( Font.MaxHeight - charDesc.Height ) - Font.Padding[ 1 ] ) * textScale;
quad[ 1 ].X := X + ( charDesc.ShiftX + charDesc.Width + Font.Padding[ 2 ] ) * textScale;
quad[ 1 ].Y := Y + ( charDesc.ShiftY + ( Font.MaxHeight - charDesc.Height ) - Font.Padding[ 1 ] ) * textScale;
quad[ 2 ].X := X + ( charDesc.ShiftX + charDesc.Width + Font.Padding[ 2 ] ) * textScale;
quad[ 2 ].Y := Y + ( charDesc.ShiftY + charDesc.Height + ( Font.MaxHeight - charDesc.Height ) + Font.Padding[ 3 ] ) * textScale;
quad[ 3 ].X := X + ( charDesc.ShiftX - Font.Padding[ 0 ] ) * textScale;
quad[ 3 ].Y := Y + ( charDesc.ShiftY + charDesc.Height + ( Font.MaxHeight - charDesc.Height ) + Font.Padding[ 3 ] ) * textScale;

And what if there are thousands of such symbols? Tens of thousands?

No, of course, the text is usually static and there is no need to constantly redraw it. But this example is from a game engine… And the more often the window in the game is updated (often), the better. But then the code will work more often! And the load will be greater.

In this case I used a different optimization.

Hidden text
unit zgl_font;

{$I zgl_config.cfg}

interface
uses
  zgl_textures,
  zgl_types,
  zgl_file,
  zgl_log,
  zgl_memory;

const
  ZGL_FONT_INFO: array[0..13] of AnsiChar = ('Z', 'G', 'L', '_', 'F', 'O', 'N', 'T', '_', 'I', 'N', 'F', 'O', #0);
  MAX_USE_FONT = 10;
  Enable       = 1;
  UseFnt       = 2;

  PaddingX1    = 0;
  PaddingX2    = 2;
  PaddingY1    = 1;
  PaddingY2    = 3;

type
  zglPCharDesc = ^zglTCharDesc;
  zglTCharDesc = record
    Page     : Word;
    Width    : Byte;
    Height   : Byte;
    ShiftX   : Integer;
    ShiftY   : Integer;
    ShiftP   : Integer;
    TexCoords: array[0..3] of zglTPoint2D;
    xx1, xx2, yy1, yy2: Single;
    _x1, _x2, _y1, _y2: Single;
  end;

type
  zglPFont = ^zglTFont;
  zglTFont = record
    Count     : record
      Pages: Word;
      Chars: Word;
                 end;

    Flags     : LongWord;
    Scale     : Single;
    ScaleNorm : Single;
    Pages     : array of zglPTexture;
    CharDesc  : array[0..65535] of zglPCharDesc;
    MaxHeight : Integer;
    MaxShiftY : Integer;
    Padding   : array[0..3] of Byte;
    TextScaleStandart : Single;
  end;

type
  zglPFontManager = ^zglTFontManager;
  zglTFontManager = record
    Count: Integer;
    Font: array[0..MAX_USE_FONT - 1] of zglPFont;
  end;
//...
procedure font_Load(var fnt: LongWord; var fntMem: zglTMemory);

var
  managerFont: zglTFontManager;

implementation 
//...
procedure font_Load(var fnt: LongWord; var fntMem: zglTMemory);
var
  i    : Integer;
  c    : LongWord;
  fntID: array[0..13] of AnsiChar;
  useFont: zglPFont;
  charDesc9, charDesc32, charDescC: zglPCharDesc;
begin
  if fnt <> zglUError then
    exit;
  fntID[13] := #0;
  mem_Read(fntMem, fntID, 13);
  if fntID <> ZGL_FONT_INFO Then
  begin
    exit;
  end;
  fnt := font_Add;
  if fnt = zglUError then
    exit;
  useFont := managerFont.Font[fnt];
  useFont^.TextScaleStandart := 0;
  mem_Read(fntMem, useFont^.Count.Pages,  2);
  mem_Read(fntMem, useFont^.Count.Chars,  2);
  mem_Read(fntMem, useFont^.MaxHeight,    4);
  mem_Read(fntMem, useFont^.MaxShiftY,    4);
  mem_Read(fntMem, useFont^.Padding[0],   4);
  SetLength(useFont^.Pages, useFont.Count.Pages);

  for i := 0 to useFont^.Count.Pages - 1 do
    useFont.Pages[i] := nil;
  for i := 0 to useFont^.Count.Chars - 1 do
  begin
    mem_Read(fntMem, c, 4);
    zgl_GetMem(Pointer(useFont^.CharDesc[c]), SizeOf(zglTCharDesc));
    charDescC := useFont^.CharDesc[c];
    mem_Read(fntMem, charDescC.Page, 4);
    mem_Read(fntMem, charDescC^.Width, 1);
    mem_Read(fntMem, charDescC^.Height, 1);
    if useFont^.TextScaleStandart < charDescC^.Width then
      useFont^.TextScaleStandart := charDescC^.Width;
    mem_Read(fntMem, charDescC^.ShiftX, 4);
    mem_Read(fntMem, charDescC^.ShiftY, 4);
    mem_Read(fntMem, charDescC^.ShiftP, 4);
    mem_Read(fntMem, charDescC.TexCoords[0], SizeOf(zglTPoint2D) * 4);
    charDescC^._x1 := charDescC^.ShiftX - useFont^.Padding[PaddingX1];
    charDescC^._x2 := charDescC^.ShiftX + charDescC^.Width + useFont^.Padding[PaddingX2];
    charDescC^._y1 := charDescC^.ShiftY + useFont^.MaxHeight - charDescC^.Height - useFont^.Padding[PaddingY1];
    charDescC^._y2 := charDescC^.ShiftY + useFont^.MaxHeight + useFont^.Padding[PaddingY2];
  end;
  if useFont^.CharDesc[32] <> nil then
    charDesc32 := useFont^.CharDesc[32]
  else
    charDesc32 := useFont^.CharDesc[49];
  if useFont^.CharDesc[32] <> nil then
    charDescC := useFont^.CharDesc[33]
  else
    charDescC := useFont^.CharDesc[49];
  charDesc32^.Width := charDescC^.Width;
  charDesc32^.Height := charDescC^.Height;
  charDesc32^.ShiftX := charDescC^.ShiftX;
  charDesc32^.ShiftY := charDescC^.ShiftY;
  charDesc32^.ShiftP := charDescC^.ShiftP;
  charDesc32^._x1 := charDescC^._x1;
  charDesc32^._x2 := charDescC^._x2;
  charDesc32^._y1 := charDescC^._y1;
  charDesc32^._y2 := charDescC^._y2;

  // "tab"
  zgl_GetMem(Pointer(useFont.CharDesc[9]), SizeOf(zglTCharDesc));
  charDesc9 := useFont.CharDesc[9];
  charDesc9^.Page := charDesc32^.Page;
  charDesc9^.Width := charDesc32^.Width * 4;
  charDesc9^.Height := charDesc32^.Height;
  charDesc9^.ShiftX := charDesc32^.ShiftX;
  charDesc9^.ShiftY := charDesc32^.ShiftY;
  charDesc9^.ShiftP := charDesc32^.ShiftP * 4;
  charDesc9^.TexCoords[0] := charDesc32^.TexCoords[0];
  charDesc9^.TexCoords[1] := charDesc32^.TexCoords[1];
  charDesc9^.TexCoords[2] := charDesc32^.TexCoords[2];
  charDesc9^.TexCoords[3] := charDesc32^.TexCoords[3];
  charDesc9^._x1 := charDesc32^._x1;
  charDesc9^._x2 := charDesc9^.ShiftX + charDesc9^.Width + useFont^.Padding[PaddingX2];
  charDesc9^._y1 := charDesc32^._y1;
  charDesc9^._y2 := charDesc32^._y2;

  if useFont^.MaxHeight > useFont^.TextScaleStandart then
    useFont^.TextScaleStandart := useFont^.MaxHeight;
  useFont := nil;
end;
unit zgl_text;

{$I zgl_config.cfg}

interface
//...

procedure text_Draw(fnt: LongWord; X, Y: Single; const Text: UTF8String; Flags: LongWord = 0); 
procedure setFontTextScale(_Scale: LongWord; fnt: LongWord);
//...
implementation 
//...
procedure setFontTextScale(_Scale: LongWord; fnt: LongWord);
var
  i: Integer;
  charDesc: zglPCharDesc;
begin
  if fnt > MAX_USE_FONT then
    exit;
  useFont := managerFont.Font[fnt];
  useFont.Scale := useFont.ScaleNorm * _Scale / 10;
  for i := 0 to 65535 do
  begin
    if Assigned(useFont.CharDesc[i]) then
      charDesc := useFont.CharDesc[i]
    else
      Continue;

    charDesc^.xx1 := charDesc^._x1 * useFont.Scale;
    charDesc^.yy1 := charDesc^._y1 * useFont.Scale;
    charDesc^.xx2 := charDesc^._x2 * useFont.Scale;
    charDesc^.yy2 := charDesc^._y2 * useFont.Scale;
  end;
end;

procedure text_Draw(fnt: LongWord; X, Y: Single; const Text: UTF8String; Flags: LongWord = 0);
var
  i, c, s : LongWord;
  charDesc: zglPCharDesc;
  quad    : array[0..3] of zglTPoint3D;
  sx      : Single;
  lastPage: Integer;
  mode    : Integer;
begin
  if fnt > MAX_USE_FONT then
    exit;
  if (Text="") or ((managerFont.Font[fnt].Flags and UseFnt) = 0) Then
    exit;
  useFont := managerFont.Font[fnt];
  for i := 0 to useFont.Count.Pages - 1 do
    if not Assigned(useFont.Pages[i]) Then exit;

  glColor4ubv(@textRGBA);

  if Off_TextScale then
    Y := Y - useFont.MaxShiftY * useScaleEx
  else
    Y := Y - useFont.MaxShiftY * useFont.Scale;

  if Flags and TEXT_HALIGN_CENTER > 0 Then
    X := X - Round(text_GetWidth(fnt, Text, textStep) / 2)
  else
    if Flags and TEXT_HALIGN_RIGHT > 0 Then
      X := X - Round(text_GetWidth(fnt, Text, textStep));
  sx := X;

  if Flags and TEXT_VALIGN_CENTER > 0 Then
    Y := Y - (useFont.MaxHeight div 2)
  else
    if Flags and TEXT_VALIGN_BOTTOM > 0 Then
      Y := Y - useFont.MaxHeight;

  FillChar(quad[0], SizeOf(zglTPoint2D) * 4, 0);
  charDesc := nil;
  lastPage := -1;
  c := utf8_GetID(Text, 1, @i);
  s := 1;
  i := 1;
  if Flags and TEXT_FX_VCA > 0 Then
    mode := GL_TRIANGLES
  else
    mode := GL_QUADS;
  if not b2dStarted Then
  begin
    if Assigned(useFont.CharDesc[c]) Then
    begin
      lastPage := useFont.CharDesc[c].Page;
      batch2d_Check(mode, FX_BLEND, useFont.Pages[useFont.CharDesc[c].Page]);

      glEnable(GL_BLEND);
      glEnable(GL_TEXTURE_2D);
      glBindTexture(GL_TEXTURE_2D, useFont.Pages[useFont.CharDesc[c].Page].ID);
      glBegin(mode);
    end else
    begin
      glEnable(GL_BLEND);
      glEnable(GL_TEXTURE_2D);
      glBegin(mode);
    end;
  end;
  while i <= Length(Text) do
  begin
    if Text[i] = #10 Then
    begin
      X := sx;
      if Off_TextScale then
        Y := Y + useFont.MaxHeight * useScaleEx
      else
        Y := Y + useFont.MaxHeight * useFont.Scale;
    end;
    c := utf8_GetID(Text, i, @i);

    if (Flags and TEXT_FX_LENGTH > 0) and (s > textLength) Then
    begin
      if s > 1 Then
      begin
        if Assigned(textLCoord) Then
        begin
          if Off_TextScale then
          begin
            textLCoord.X := quad[0].X + useFont.Padding[PaddingX1] * useScaleEx;
            textLCoord.Y := quad[0].Y + useFont.Padding[PaddingY1] * useScaleEx;
          end
          else begin
            textLCoord.X := quad[0].X + useFont.Padding[PaddingX1] * useFont.Scale;
            textLCoord.Y := quad[0].Y + useFont.Padding[PaddingY1] * useFont.Scale;
          end;
        end;
        if Assigned(textLCharDesc) Then
          textLCharDesc^ := charDesc^;
      end;
      break;
    end;
    INC(s);

    charDesc := useFont.CharDesc[c];
    if (c = 10) and (c = 13) then
      Continue;
    if not Assigned(charDesc) Then
      charDesc := useFont.CharDesc[63];


    if lastPage <> charDesc.Page Then
    begin
      lastPage := charDesc.Page;

      if (not b2dStarted) Then
      begin
        glEnd();

        glBindTexture(GL_TEXTURE_2D, useFont.Pages[charDesc.Page].ID);
        glBegin(mode);
      end else
        if batch2d_Check(mode, FX_BLEND, useFont.Pages[charDesc.Page]) Then
        begin
          glEnable(GL_BLEND);

          glEnable(GL_TEXTURE_2D);
          glBindTexture(GL_TEXTURE_2D, useFont.Pages[charDesc.Page].ID);
          glBegin(mode);
        end;
    end;

    if Off_TextScale then
    begin
      quad[0].X := X + (charDesc.ShiftX - useFont.Padding[PaddingX1]) * useScaleEx;
      quad[0].Y := Y + (charDesc.ShiftY + useFont.MaxHeight - charDesc.Height - useFont.Padding[PaddingY1]) * useScaleEx;

      quad[1].X := X + (charDesc.ShiftX + charDesc.Width + useFont.Padding[PaddingX2]) * useScaleEx;
      quad[2].Y := Y + (charDesc.ShiftY + useFont.MaxHeight + useFont.Padding[PaddingY2]) * useScaleEx;
    end else
    begin
      quad[0].X := X + charDesc.xx1;
      quad[0].Y := Y + charDesc.yy1;

      quad[1].X := X + charDesc.xx2;
      quad[2].Y := Y + charDesc.yy2;
    end;
    quad[1].Y := quad[0].Y;
    quad[2].X := quad[1].X;
    quad[3].X := quad[0].X;
    quad[3].Y := quad[2].Y;
    quad[0].Z := 0;
    quad[1].Z := 0;
    quad[2].Z := 0;
    quad[3].Z := 0;

    if Flags and TEXT_FX_VCA > 0 Then
    begin
      {$IfDef USE_GLES}_glColor4f{$Else}glColor4f{$EndIf}(fx2dVCA[0, 0], fx2dVCA[0, 1], fx2dVCA[0, 2], fx2dVCA[0, 3]);
      glTexCoord2fv(@charDesc.TexCoords[0]);
      glVertex3fv(@quad[0]);

      {$IfDef USE_GLES}_glColor4f{$Else}glColor4f{$EndIf}(fx2dVCA[1, 0], fx2dVCA[1, 1], fx2dVCA[1, 2], fx2dVCA[1, 3]);
      glTexCoord2fv(@charDesc.TexCoords[1]);
      glVertex3fv(@quad[1]);

      {$IfDef USE_GLES}_glColor4f{$Else}glColor4f{$EndIf}(fx2dVCA[2, 0], fx2dVCA[2, 1], fx2dVCA[2, 2], fx2dVCA[2, 3]);
      glTexCoord2fv(@charDesc.TexCoords[2]);
      glVertex3fv(@quad[2]);

      {$IfDef USE_GLES}_glColor4f{$Else}glColor4f{$EndIf}(fx2dVCA[2, 0], fx2dVCA[2, 1], fx2dVCA[2, 2], fx2dVCA[2, 3]);
      glTexCoord2fv(@charDesc.TexCoords[2]);
      glVertex3fv(@quad[2]);

      {$IfDef USE_GLES}_glColor4f{$Else}glColor4f{$EndIf}(fx2dVCA[3, 0], fx2dVCA[3, 1], fx2dVCA[3, 2], fx2dVCA[3, 3]);
      glTexCoord2fv(@charDesc.TexCoords[3]);
      glVertex3fv(@quad[3]);

      {$IfDef USE_GLES}_glColor4f{$Else}glColor4f{$EndIf}(fx2dVCA[1, 0], fx2dVCA[1, 1], fx2dVCA[1, 2], fx2dVCA[1, 3]);
      glTexCoord2fv(@charDesc.TexCoords[0]);
      glVertex3fv(@quad[0]);
    end else
    begin
      glTexCoord2fv(@charDesc.TexCoords[0]);
      glVertex3fv(@quad[0]);

      glTexCoord2fv(@charDesc.TexCoords[1]);
      glVertex3fv(@quad[1]);

      glTexCoord2fv(@charDesc.TexCoords[2]);
      glVertex3fv(@quad[2]);

      glTexCoord2fv(@charDesc.TexCoords[3]);
      glVertex3fv(@quad[3]);
    end;

    if Off_TextScale then
      X := X + (charDesc.ShiftP + textStep) * useScaleEx
    else
      X := X + (charDesc.ShiftP + textStep) * useFont.Scale;
  end;

  if not b2dStarted Then
  begin
    glEnd();

    glDisable(GL_TEXTURE_2D);
    glDisable(GL_BLEND);
  end;
end;

Source code Here.

and ended up with this code:

if Off_TextScale then
begin
  quad[0].X := X + (charDesc.ShiftX - useFont.Padding[PaddingX1]) * useScaleEx;
  quad[0].Y := Y + (charDesc.ShiftY + useFont.MaxHeight - charDesc.Height - useFont.Padding[PaddingY1]) * useScaleEx;

  quad[1].X := X + (charDesc.ShiftX + charDesc.Width + useFont.Padding[PaddingX2]) * useScaleEx;
  quad[2].Y := Y + (charDesc.ShiftY + useFont.MaxHeight + useFont.Padding[PaddingY2]) * useScaleEx;
end else
begin
  quad[0].X := X + charDesc.xx1;
  quad[0].Y := Y + charDesc.yy1;

  quad[1].X := X + charDesc.xx2;
  quad[2].Y := Y + charDesc.yy2;
end;
quad[1].Y := quad[0].Y;
quad[2].X := quad[1].X;
quad[3].X := quad[0].X;
quad[3].Y := quad[2].Y;

as a result first some of the code is executed only in exceptional cases, and second the working part, where the calculations were performed at the beginning of the program initialization, and the data is simply used in a frequently used function with a loop.

The data structure has been changed, the loading and processing of data has been changed. Considering that the optimizations Pascal wish for the best, then this code has changed not only in the code Pascalbut also very strongly in the final assembler code (you can check this yourself, take ZenGL version 3.12 and latest version, make an assembler output and see the result).

I wanted to draw attention to the code itself! And not to the fact that it is outdated. OpenGL! This problem is clearly shown in this code and similar actions can be used in quite a large amount of code.

Now you should pay attention, such optimizations cannot be done by the compiler at the moment. The compiler does not look at the code and does not analyze it in the form in which a person can see it. The compiler will not link two modules together until you specify it. And it certainly will not be able to link data from one module to a function in another and redo all of this: both the data and the function (although compilers do cope with certain optimizations better than many programmers).

So what about optimization?

What you should remember is that it is often not worth spending time on optimization. Optimization can take a lot of time, and may require you to study in detail the code you want to optimize and the code that is related to the code you want to optimize (and may also require you to study the code that is related to the code that is related to the code you are optimizing). This is not an easy task!

Try to use ready-made optimizations. Study the PL you use, study the engine you use. Much has been implemented before us.

You will need optimization when you see that the program you are developing is starting to consume a lot of resources and work slowly. Find out the main reason why your program is working slowly. For this, you can use profilerand someone else can guess where the program has problems in the code.

Be aware that the profiler will be able to show you the load in certain procedures/functions, but you may need to optimize the code in loaded functions in different ways, and not just by changing the code in the loaded function itself.

There may be many tips for optimizing certain actions:

  • expand cycles.

  • try to get rid of the cycles.

  • get rid of deleting/creating objects (if there is no need for it).

  • use static (already calculated) data.

  • use reference data.

  • choose to use arrays or heaps, in different situations they can give different optimal performance.

  • much more.

Is it bad that you are engaged in optimization for your general education?

No, such things will never be bad. They are bad because you spend time on them. But in this way you begin to understand more fully how the code works (especially if this is interesting and important to you).

Maybe it's worth studying this topic? After all, many people have already tried to optimize different code, maybe there are many articles on the Internet? Many books?

Never forget to use publicly available resources to study information! There is a lot of it in the public domain now!

The end.

Optimization is very important especially in games. Nowadays, few people try to optimize programs until they start working poorly or until users start reporting it. Many developers approach this topic with the attitude “it works fine for me, so everything is fine.” Previously, they tried to test programs so that they would work on weaker machines than people themselves had. This is correct, because in this way you can reach a larger number of users of your program. But for some reason, many forget about this…

This concludes this article. I have covered a very small part of optimization, but I hope I have explained this topic clearly enough. I did not want to make a very long article.

I also post videos on my channelwhere I touch on various topics related to programming and more.

If you liked the article, like it.

Didn't like it? – Dislike it! )))

Any of your opinions are useful! Perhaps you will also contribute to this article by writing a huge post that will be longer than my article. And this will provide more information to people. )))

Good luck!

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *