HellRok / Taylor

A simple game engine built using raylib and mruby
https://www.taylormadetech.dev
MIT License
97 stars 6 forks source link

Performance Discussion #26

Open Nathan-MV opened 5 months ago

Nathan-MV commented 5 months ago

I wonder what's going on here as i doubt it's entirely Ruby's fault, LiteRGSS2 (LiteCGSS is the backend with SFML2 and LiteRGSS2 are the Ruby bindings) can draw around 50k at a stable 60 fps (or is SFML2 just faster than Raylib/SDL2 and i didn't know? xD (SDL2 seems to draw 10k at a stable 60)) https://gitlab.com/NuriYuri/litecgss https://gitlab.com/pokemonsdk/litergss2

Benchmark: https://www.mediafire.com/file/zi7d3qwc0nk93dd/litergss2_-_bench.7z/file

awfulcooking commented 5 months ago

mruby can handle ~50,000 Ruby method calls per frame at 60fps on low/mid range devices.

MRI can do around 4x that.

nb. Calling a block has the same overhead as calling a method.

So with:

array = [1] * 10000

This loop costs 10000 more method calls:

array.each do |el|
  # do nothing
end

Than:

n = array.size
i = 0
while i < n
  el = array[i]
  i += 1
  # do nothing
end

Instance variables are free to access, relative to calling a method.

As a result it's much more efficient to ask game objects to handle themselves:

e.g.

class Tile
  attr_accessor %i(x y w h vx vy color)
  def tick
    if @vx != 0
      @x += @vx
    end
    if @vy != 0
      @y += @vy
    end
    DrawRectangle(@x, @y, @w, @h, @color)
  end
end

def tick()
  n = world.size
  i = 0
  while i < n
    thing = world[i]
    thing.tick
    i += 1
  end
end

This is the fastest way I know to update and draw many game objects in mRuby.

I bet the difference between LiteRGSS2, Taylor and SDL in your tests comes down to method call overhead in the implementation of their Ruby APIs.

Once they call into C, the difference is probably negligible in comparison!

Nathan-MV commented 5 months ago

I had tested SDL2 using mkxp-z and it gave 10k but using RGU which is using SDL3 it gives 50 fps with 20k both of them using Angle to Vulkan, as they are used to do the same thing, aka being a RGSS1/2/3 player for RPG Maker XP/VX/VXA i used the same benchmark for both (LiteRGSS is a little different (It was initially made for RGSS1(RPG Maker XP) but then it was designed solely for PokemonSDK and changed things too much on both ends) so it needs some heavy modifications and instead of being an executable it is a shared library so i put the ruby binary pokemonsdk uses with it in the download)

class SpriteGenerator
  DEFAULT_SPRITE_COUNT = 20_000
  SCREEN_WIDTH = 640
  SCREEN_HEIGHT = 480
  SPRITE_SIZE = 32

  def initialize
    @sprite_count = DEFAULT_SPRITE_COUNT
    @viewport = Viewport.new(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT)
    @bitmap = nil
  end

  def run
    initialize_sprites
    loop do
      Graphics.update
    end
  rescue StandardError
    puts "\nEND\n"
  end

  private

  def initialize_sprites
    puts "Sprite Count: #{@sprite_count}"

    @sprites = @sprite_count.times.map do |i|
      initialize_bitmap if (i % 100).zero?
      initialize_sprite
    end
  end

  def initialize_bitmap
    @bitmap = Bitmap.new(320, 320)
    @bitmap.fill_rect(0, 0, 320, 320, random_color)
  end

  def initialize_sprite
    sprite = Sprite.new(@viewport)
    sprite.bitmap = @bitmap
    sprite.x = rand(SCREEN_WIDTH) - SPRITE_SIZE
    sprite.y = rand(SCREEN_HEIGHT) - SPRITE_SIZE
    set_sprite_src_rect(sprite)
    sprite
  end

  def set_sprite_src_rect(sprite)
    j = @sprite_count % 100
    sprite.src_rect.set(32 * (j / 10), 32 * (j % 10), SPRITE_SIZE, SPRITE_SIZE)
  end

  def random_color
    Color.new(rand(256), rand(256), rand(256), 32)
  end
end

SpriteGenerator.new.run
HellRok commented 5 months ago

I've been busy with the refactor at the moment but performance is something I've kept a little bit of an eye on. I'm probably going to have a quick pass over the code base once I've finished the refactor to see what I can do about performance.

With the refactor there is a lot less ruby -> ruby -> c++, it's now more ruby -> c++, I'm hoping this will help mitigate a little bit of the performance as I too feel it could be better.

Nathan-MV commented 5 months ago

Ray said he tends to ignore code that could be more performatic for code that are simpler to understand for the sake of his students

Nathan-MV commented 4 months ago

It appears that creating your own Vector2 class in Ruby increases benchmark performance by 2x https://github.com/vaiorabbit/raylib-bindings/blob/main/examples/textures_bunnymark_optimized.rb