raylib-zig icon indicating copy to clipboard operation
raylib-zig copied to clipboard

Performance difference compared to ryupold/raylib.zig

Open WeijieH opened this issue 7 months ago • 2 comments

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.

WeijieH avatar Jul 20 '24 17:07 WeijieH