Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Performance difference compared to ryupold/raylib.zig #122

Open
WeijieH opened this issue Jul 20, 2024 · 2 comments
Open

Performance difference compared to ryupold/raylib.zig #122

WeijieH opened this issue Jul 20, 2024 · 2 comments

Comments

@WeijieH
Copy link

WeijieH commented Jul 20, 2024

Hi, first thanks for your great work. It just makes raylib bindings much easier to use.
Recently I have noticed some performance difference when using ryupold's binding.
Just to be clear, it should not be the problem at your end. Since when I either build raylib from source or use prebuild libs from raylib's official release page, I get the same performance. However, when I use ryupold's raylib.zig, the performance is better.
My small project is simply trying to redo some drawings with opensimplex noise showed in codingtrain's video in zig.
There is the tree of this project:
image
The content of main.zig is:

const std = @import("std");
const pi = std.math.pi;
const raylib = @import("raylib");
const opensimplex = @cImport({
    @cInclude("OpenSimplex2F.c");
});

// const heart = @import("heart.zig");
const heart = struct {
    seed: f32,
    offset: f32,
    alpha: f32,
    width: f32,
    part: f32,
    zoom: f32,
    factor: f32,
    speed: f32,
};

pub fn main() !void {
    const fps: i32 = 60;
    const m: u32 = 1000;
    const lines: u32 = 150;
    // raylib.setConfigFlags(raylib.ConfigFlags{ .msaa_4x_hint = true, .vsync_hint = true });
    raylib.initWindow(800, 800, "heart");
    defer raylib.closeWindow();
    raylib.setTargetFPS(fps);

    var ctx: ?*opensimplex.OpenSimplex2F_context = undefined;
    _ = opensimplex.OpenSimplex2F(42, &ctx);
    defer {
        opensimplex.OpenSimplex2F_free(ctx);
        opensimplex.OpenSimplex2F_shutdown();
    }

    var rand_impl = std.rand.DefaultPrng.init(2024);
    var rand = rand_impl.random();

    var hearts: [lines]heart = undefined;
    var line: [m]raylib.Vector2 = undefined;
    var i: u32 = 0;
    var frame: f32 = undefined;
    for (&hearts) |*h| {
        h.seed = 1024 + 2024 * rand.float(f32);
        h.offset = 2 * pi * rand.float(f32);
        h.alpha = 0.05 + 0.1 * rand.float(f32);
        h.width = 1 + 3.5 * (1 - std.math.pow(f32, rand.float(f32), 2));
        h.part = 0.8 + 0.3 * std.math.pow(f32, rand.float(f32), 3);
        // h.part = 1;
        h.factor = 5 + 4.5 * rand.float(f32);
        h.speed = 0.5 + 1.5 * rand.float(f32);
        h.zoom = 0.65 + 0.5 * rand.float(f32);
        // std.debug.print("{},{},{},{},{},{},{},{}\n", h.*);
    }

    while (!raylib.windowShouldClose()) : (i += 1) {
        raylib.beginDrawing();
        defer raylib.endDrawing();

        raylib.clearBackground(raylib.Color.ray_white);
        raylib.drawFPS(10, 10);

        frame = @as(f32, @floatFromInt(i % fps)) / fps;

        for (hearts) |h| {
            generate_points(&line, ctx, frame, 20, h);
            raylib.drawSplineLinear(&line, m, h.width, raylib.fade(raylib.Color.black, h.alpha));
        }
    }
}

pub fn generate_points(line: []raylib.Vector2, ctx: ?*opensimplex.OpenSimplex2F_context, frame: f32, radius: f32, c: heart) void {
    var theta: f32 = undefined;
    var x: f32 = undefined;
    var y: f32 = undefined;
    var phase: f32 = undefined;
    var p: f32 = undefined;

    for (line, 0..) |*item, i| {
        p = @as(f32, @floatFromInt(i)) / @as(f32, @floatFromInt(line.len - 1));
        theta = c.offset + c.part * 2 * pi * p;
        x = radius * 16 * std.math.pow(f32, @sin(theta), 3);
        y = -radius * (13 * @cos(theta) - 5 * @cos(2 * theta) - 2 * @cos(3 * theta) - @cos(4 * theta));
        phase = 2 * pi * (9.4 * p - frame);
        item.x = c.zoom * x + @as(f32, @floatCast(400 + c.factor * std.math.pow(f32, p + 0.1, 2) * opensimplex.OpenSimplex2F_noise2(ctx, c.seed + c.speed * @cos(phase), c.speed * @sin(phase))));
        item.y = c.zoom * y + @as(f32, @floatCast(350 + c.factor * std.math.pow(f32, p + 0.1, 2) * opensimplex.OpenSimplex2F_noise2(ctx, 2 * c.seed + c.speed * @cos(phase), c.speed * @sin(phase))));
    }
    return;
}

Opensimplex2F.c and Opensimplex2F.h are from https://github.com/KdotJPG/OpenSimplex2/tree/master/_old/c
Here is build.zig:

const std = @import("std");
const rlz = @import("raylib-zig");

pub fn build(b: *std.Build) !void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const raylib_dep = b.dependency("raylib-zig", .{
        .target = target,
        .optimize = optimize,
    });

    const raylib = raylib_dep.module("raylib");
    const raylib_artifact = raylib_dep.artifact("raylib");

    //web exports are completely separate
    if (target.query.os_tag == .emscripten) {
        const exe_lib = rlz.emcc.compileForEmscripten(b, "zig-heart-raylib", "src/main.zig", target, optimize);

        exe_lib.linkLibrary(raylib_artifact);
        exe_lib.root_module.addImport("raylib", raylib);

        // Note that raylib itself is not actually added to the exe_lib output file, so it also needs to be linked with emscripten.
        const link_step = try rlz.emcc.linkWithEmscripten(b, &[_]*std.Build.Step.Compile{ exe_lib, raylib_artifact });

        b.getInstallStep().dependOn(&link_step.step);
        const run_step = try rlz.emcc.emscriptenRunStep(b);
        run_step.step.dependOn(&link_step.step);
        const run_option = b.step("run", "Run zig-heart-raylib");
        run_option.dependOn(&run_step.step);
        return;
    }

    const exe = b.addExecutable(.{ .name = "zig-heart-raylib", .root_source_file = b.path("src/main.zig"), .optimize = optimize, .target = target });

    exe.linkLibrary(raylib_artifact);
    exe.addIncludePath(b.path("./src"));
    exe.root_module.addImport("raylib", raylib);

    const run_cmd = b.addRunArtifact(exe);
    const run_step = b.step("run", "Run zig-heart-raylib");
    run_step.dependOn(&run_cmd.step);

    b.installArtifact(exe);
}

I build and run script in releasefast mode, and the average fps I get is around 40.

$ zig build --release=fast run

image


Now, when I switch to ryupold's bindings. The average fps I get with same code, same releasefast mode, is about 60 fps.
image

Here is the project tree for ryupold's binding.
image

Raylib folder is getting from:

cd $YOUR_SRC_FOLDER
git submodule add https://github.com/ryupold/raylib.zig raylib
git submodule update --init --recursive

main.zig is basically the same except some API name changes. For example, in your binding, it is raylib.initWindow(), but in ryupold's, it would be raylib.InitWindow()

And here is build.zig:

const std = @import("std");
const raylib = @import("raylib/build.zig");

pub fn build(b: *std.Build) !void {
    // const target = b.standardTargetOptions(.{});
    const target = std.Target.Query{ .os_tag = .windows, .cpu_arch = .x86_64, .abi = .gnu };
    const optimize = b.standardOptimizeOption(.{});
    const exe = b.addExecutable(.{
        .name = "zig_ray",
        .root_source_file = b.path("src/main.zig"),
        .target = b.resolveTargetQuery(target),
        .optimize = optimize,
    });
    exe.addIncludePath(b.path("./src"));
    exe.linkLibC();
    raylib.addTo(b, exe, target, optimize, .{});

    b.installArtifact(exe);

    const run_cmd = b.addRunArtifact(exe);

    run_cmd.step.dependOn(b.getInstallStep());

    if (b.args) |args| {
        run_cmd.addArgs(args);
    }

    const run_step = b.step("run", "Run the app");
    run_step.dependOn(&run_cmd.step);
}

Still I don't see any obvious difference but just very curious why I'm getting nearly 20 fps improvement just by using a different binding.

@Not-Nik
Copy link
Owner

Not-Nik commented Jul 22, 2024

iirc ryupolds binding doesnt use latest raylib, so maybe this is caused by a regression within raylib. Could you try using an older version in your source build?

@WeijieH
Copy link
Author

WeijieH commented Jul 22, 2024

Should not be the version difference. Here are my other tests.
First, both your and ryupolds binding are based on version 5.1 dev. The exact commit is different but I'm not able to switch to ryupolds binding commit because it doesn't support zig 0.13 out of box. Rupold provided a customized build.zig to support zig 0.13.
Despite this difference, the output/log msg from both builds are exactly same.
Here it is:

INFO: Initializing raylib 5.1-dev
INFO: Platform backend: DESKTOP (GLFW)
INFO: Supported raylib modules:
INFO:     > rcore:..... loaded (mandatory)
INFO:     > rlgl:...... loaded (mandatory)
INFO:     > rshapes:... loaded (optional)
INFO:     > rtextures:. loaded (optional)
INFO:     > rtext:..... loaded (optional)
INFO:     > rmodels:... loaded (optional)
INFO:     > raudio:.... loaded (optional)
INFO: DISPLAY: Device initialized successfully
INFO:     > Display size: 3840 x 2160
INFO:     > Screen size:  800 x 800
INFO:     > Render size:  800 x 800
INFO:     > Viewport offsets: 0, 0
INFO: GLAD: OpenGL extensions loaded successfully
INFO: GL: Supported extensions count: 403
INFO: GL: OpenGL device information:
INFO:     > Vendor:   NVIDIA Corporation
INFO:     > Renderer: NVIDIA GeForce RTX 4090/PCIe/SSE2
INFO:     > Version:  3.3.0 NVIDIA 560.70
INFO:     > GLSL:     3.30 NVIDIA via Cg compiler
INFO: GL: VAO extension detected, VAO functions loaded successfully
INFO: GL: NPOT textures extension detected, full NPOT textures supported
INFO: GL: DXT compressed textures supported
INFO: GL: ETC2/EAC compressed textures supported
INFO: GLFW platform: Win32
INFO: PLATFORM: DESKTOP (GLFW): Initialized successfully
INFO: TEXTURE: [ID 1] Texture loaded successfully (1x1 | R8G8B8A8 | 1 mipmaps)
INFO: TEXTURE: [ID 1] Default texture loaded successfully
INFO: SHADER: [ID 1] Vertex shader compiled successfully
INFO: SHADER: [ID 2] Fragment shader compiled successfully
INFO: SHADER: [ID 3] Program shader loaded successfully
INFO: SHADER: [ID 3] Default shader loaded successfully
INFO: RLGL: Render batch vertex buffers loaded successfully in RAM (CPU)
INFO: RLGL: Render batch vertex buffers loaded successfully in VRAM (GPU)
INFO: RLGL: Default OpenGL state initialized successfully
INFO: TEXTURE: [ID 2] Texture loaded successfully (128x128 | GRAY_ALPHA | 1 mipmaps)
INFO: FONT: Default font loaded successfully (224 glyphs)
INFO: TIMER: Target time per frame: 16.667 milliseconds
INFO: TEXTURE: [ID 2] Unloaded texture data from VRAM (GPU)
INFO: SHADER: [ID 3] Default shader unloaded successfully
INFO: TEXTURE: [ID 1] Default texture unloaded successfully
INFO: Window closed successfully

So I had the following test.
Test 1:
Download raylib 5.0 release and link to the libs directly. The average I got is around 40 fps.
Test 2:
Git clone latest master branch (5.5) and cimport to zig manually. Average fps is still 40 fps.

The performance does not change with version number.
Then I did my final test, ran both projects in debug mode and now the fps are same, around 15 fps.
The only difference I can think of is the customized build.zig script from ryupolds binding. But I don't see anything special in it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants