Welcome to ZigFall!

Interfaces in Zig and Platform Abstractions

I’ve moved code around again, in order to abstract away the platform layer, and work on top of higher level abstractions. You can reach this version of the code at v0.3.0.

Here is an example of how an interface is used in Zig:

This is the SpriteBatch Interface.

pub const SpriteBatch = struct {
    ptr: *anyopaque,
    vtable: *const VTable,

    pub const VTable = struct {
        drawSprite: *const fn (ctx: *anyopaque, source_rect: Rect, dest_rect: Rect) void,
        drawBg: *const fn (ctx: *anyopaque, source_rect: Rect, dest_rect: Rect) void,
    };

    const Self = @This();
    pub fn draw_spr(self: Self, sx: f32, sy: f32, sw: f32, sh: f32, dx: f32, dy: f32, scale_x: f32, scale_y: f32) void {
        const source_rect: Rect = .{ .x = sx, .y = sy, .width = sw, .height = sh };
        const dest_rect: Rect = .{ .x = dx, .y = dy, .width = sw * scale_x, .height = sh * scale_y };
        self.vtable.drawSprite(self.ptr, source_rect, dest_rect);
    }
    pub fn draw_bg(self: Self, sx: f32, sy: f32, sw: f32, sh: f32, dx: f32, dy: f32, scale_x: f32, scale_y: f32) void {
        const source_rect: Rect = .{ .x = sx, .y = sy, .width = sw, .height = sh };
        const dest_rect: Rect = .{ .x = dx, .y = dy, .width = sw * scale_x, .height = sh * scale_y };
        self.vtable.drawBg(self.ptr, source_rect, dest_rect);
    }
};

This is the platform specific implementation:

pub const MySpriteBatch = struct {
    const Self = @This();

    platform: MyPlatform,

    fn drawSpriteImpl(ctx: *anyopaque, source_rect: Rect, dest_rect: Rect) void {
        const self: *Self = @ptrCast(@alignCast(ctx));

        self.platform.batch.drawSprite(&self.platform.resources.texSprites, source_rect, dest_rect) catch unreachable;
    }
    fn drawBgImpl(ctx: *anyopaque, source_rect: Rect, dest_rect: Rect) void {
        const self: *Self = @ptrCast(@alignCast(ctx));

        self.platform.batch.drawSprite(&self.platform.resources.texBackground, source_rect, dest_rect) catch unreachable;
    }

    const vtable = SpriteBatch.VTable{
        .drawSprite = drawSpriteImpl,
        .drawBg = drawBgImpl,
    };

    pub fn spriteBatch(self: *Self) SpriteBatch {
        return .{
            .ptr = self,
            .vtable = &vtable,
        };
    }
};

Just be careful playing with pointers, they are the main source of segmentation faults that can be tricky to debug.

This is an old Scene abstraction, I classically wrote, but this is not how I will structure my code.


pub const Scene = struct {
    batch: SpriteBatch,

    const Self = @This();
    pub fn init(batch: SpriteBatch) Self {

        return .{ .batch = batch };
    }

    pub fn update(self: *Self, dt: f32) void {
        _ = self;
        _ = dt;
    }

    pub fn render(self: *Self) void {
        self.batch.draw_bg(0, 0, 100, 100, 5, 5, 20, 20);

        self.batch.draw_spr(0, 0, 32, 32, 50, 50, 2, 2);
        self.batch.draw_spr(0, 0, 32, 32, 0, 50, 2, 2);
        self.batch.draw_spr(0, 0, 32, 32, 150, 50, 2, 2);
        self.batch.draw_spr(0, 0, 32, 32, 250, 50, 2, 2);
    }
};

The problem is scene should not hold a SpriteBatch, and update and render functions should be outside of the Scene. More like this:

pub const Scene = struct {
    animation: anim.FramesAnimation,

    const Self = @This();
    pub fn init() Self {
        const source_rect: math.Box = .init(0, 0, 32, 32);
        const dest_rect: math.Box = .init(0, 0, 64, 64);
        const frames = &[_]u8{ 0, 1, 2, 3, 4 };
        return .{ .animation = .init(7.2, frames, source_rect, dest_rect) };
    }
};

pub fn renderScene(batch: SpriteBatch, scene: *Scene, alpha: f32) void {
    _ = alpha;
    batch.draw_bg(0, 0, 64, 64, 5, 5, 10, 10);
    batch.draw_spr(0, 0, 32, 32, 0, 0, 2, 2);
    scene.animation.dest.x = 0;
    scene.animation.dest.y = 0;
    anim.renderFramesAnimation(batch, &scene.animation);
    {
        const i = 1;
        scene.animation.dest.x = @floatFromInt(i % 30 * 30);
        scene.animation.dest.y = @floatFromInt(i / 30 * 30);
        anim.renderFramesAnimation(batch, &scene.animation);
    }

    //for (0..1000) |i| {
    //    scene.animation.dest.x = @floatFromInt(i % 30 * 30);
    //    scene.animation.dest.y = @floatFromInt(i / 30 * 30);
    //    //anim.renderFramesAnimation(batch, &scene.animation);
    //}
}

pub fn updateScene(scene: *Scene, dt: f64) void {
    anim.updateFramesAnimation(&scene.animation, dt);
}

Fix One bug in the Sprite Batch Renderer

I have noticed some flickering after minimizing and restoring the window, through some iterations I think I found the culprit. Recall MyBatchDraw flush method looks like this:

    fn flush(self: *Self) !void {
        if (self.sprite_count == 0) return;

        self.SetShaderResourceForTexture(self.current_texture.?);

        self.context.Unmap(@ptrCast(self.vertex_buffer), 0);

        self.context.DrawIndexed(self.sprite_count * 6, 0, 0);

        const hr = self.context.Map(
            @ptrCast(self.vertex_buffer),
            0,
            //D3D11_MAP_WRITE_NO_OVERWRITE,
            D3D11_MAP_WRITE_DISCARD,
            0,
            &self.mapped,
        );
        if (hr != HRESULT.S_OK) return error.MapFailed;
        self.sprite_count = 0;
    }

So I Looked at it with DebugView.exe and found this DirectX Error that reads like this: draw cannot be invoked while a bound resource is currently mapped.

Turns out after the final flush, we hold on to the final Map as you can see above. This lead to corruption and flickering.

The fix was to write an endBatch method and call it at the end of the batch drawing.

    fn endBatch(self: *Self) !void {
        try self.flush();

        self.context.Unmap(@ptrCast(self.vertex_buffer), 0);
    }

As always you can reach this version of the code at v0.3.1. Have a nice day.

Draw Sprites From a Texture Atlas And Batched Rendering
1px Shapes with LINESTRIP with MyDebugBatch