311 lines
8.6 KiB
Zig
311 lines
8.6 KiB
Zig
const sokol = @import("sokol");
|
|
const sg = sokol.gfx;
|
|
const sglue = sokol.glue;
|
|
const slog = sokol.log;
|
|
const sapp = sokol.app;
|
|
const simgui = sokol.imgui;
|
|
const sgl = sokol.gl;
|
|
|
|
const Math = @import("./math.zig");
|
|
const Vec2 = Math.Vec2;
|
|
const Vec4 = Math.Vec4;
|
|
const rgb = Math.rgb;
|
|
const Rect = Math.Rect;
|
|
|
|
const std = @import("std");
|
|
const log = std.log.scoped(.graphics);
|
|
const assert = std.debug.assert;
|
|
|
|
const imgui = @import("imgui.zig");
|
|
const tracy = @import("tracy");
|
|
const fontstash = @import("./fontstash/root.zig");
|
|
pub const Font = fontstash.Font;
|
|
|
|
// TODO: Seems that there is a vertical jitter bug when resizing a window in OpenGL. Seems like a driver bug.
|
|
// From other peoples research it seems that disabling vsync when a resize event occurs fixes it.
|
|
// Maybe a patch for sokol could be made?
|
|
// More info:
|
|
// * https://github.com/libsdl-org/SDL/issues/11618
|
|
// * https://github.com/nimgl/nimgl/issues/59
|
|
|
|
const Options = struct {
|
|
const ImguiFont = struct {
|
|
ttf_data: []const u8,
|
|
size: f32 = 16
|
|
};
|
|
|
|
allocator: std.mem.Allocator,
|
|
logger: sg.Logger = .{},
|
|
imgui_font: ?ImguiFont = null
|
|
};
|
|
|
|
const DrawFrame = struct {
|
|
screen_size: Vec2 = Vec2.zero,
|
|
bg_color: Vec4 = rgb(0, 0, 0),
|
|
|
|
scissor_stack_buffer: [32]Rect = undefined,
|
|
scissor_stack: std.ArrayListUnmanaged(Rect) = .empty,
|
|
|
|
fn init(self: *DrawFrame) void {
|
|
self.* = DrawFrame{
|
|
.scissor_stack = .initBuffer(&self.scissor_stack_buffer)
|
|
};
|
|
}
|
|
};
|
|
|
|
var draw_frame: DrawFrame = undefined;
|
|
var main_pipeline: sgl.Pipeline = .{};
|
|
var linear_sampler: sg.Sampler = .{};
|
|
var nearest_sampler: sg.Sampler = .{};
|
|
var font_context: fontstash.Context = undefined;
|
|
pub var font_resolution_scale: f32 = 1;
|
|
|
|
pub fn init(options: Options) !void {
|
|
draw_frame.init();
|
|
|
|
sg.setup(.{
|
|
.logger = options.logger,
|
|
.environment = sglue.environment(),
|
|
});
|
|
|
|
sgl.setup(.{
|
|
.logger = .{
|
|
.func = options.logger.func,
|
|
.user_data = options.logger.user_data
|
|
}
|
|
});
|
|
|
|
main_pipeline = sgl.makePipeline(.{
|
|
.colors = init: {
|
|
var colors: [8]sg.ColorTargetState = @splat(.{});
|
|
colors[0] = .{
|
|
.blend = .{
|
|
.enabled = true,
|
|
.src_factor_rgb = .SRC_ALPHA,
|
|
.dst_factor_rgb = .ONE_MINUS_SRC_ALPHA,
|
|
.op_rgb = .ADD,
|
|
.src_factor_alpha = .ONE,
|
|
.dst_factor_alpha = .ONE_MINUS_SRC_ALPHA,
|
|
.op_alpha = .ADD,
|
|
},
|
|
};
|
|
break :init colors;
|
|
},
|
|
});
|
|
|
|
imgui.setup(options.allocator, .{
|
|
.logger = .{
|
|
.func = options.logger.func,
|
|
.user_data = options.logger.user_data
|
|
},
|
|
.no_default_font = options.imgui_font != null,
|
|
|
|
// TODO: Figure out a way to make imgui play nicely with UI
|
|
// Ideally when mouse is inside a Imgui window, then the imgui cursor should be used.
|
|
// Otherwise our own cursor should be used.
|
|
.disable_set_mouse_cursor = true
|
|
});
|
|
|
|
if (options.imgui_font) |imgui_font| {
|
|
imgui.addFont(imgui_font.ttf_data, imgui_font.size);
|
|
}
|
|
|
|
linear_sampler = sg.makeSampler(.{
|
|
.min_filter = .LINEAR,
|
|
.mag_filter = .LINEAR,
|
|
.mipmap_filter = .LINEAR,
|
|
.label = "linear-sampler",
|
|
});
|
|
|
|
nearest_sampler = sg.makeSampler(.{
|
|
.min_filter = .NEAREST,
|
|
.mag_filter = .NEAREST,
|
|
.mipmap_filter = .NEAREST,
|
|
.label = "nearest-sampler",
|
|
});
|
|
|
|
const dpi_scale = sapp.dpiScale();
|
|
const atlas_size = 512;
|
|
const atlas_dim = std.math.ceilPowerOfTwoAssert(u32, @intFromFloat(atlas_size * dpi_scale));
|
|
font_context = try fontstash.Context.init(.{
|
|
.width = @intCast(atlas_dim),
|
|
.height = @intCast(atlas_dim),
|
|
});
|
|
}
|
|
|
|
pub fn deinit() void {
|
|
imgui.shutdown();
|
|
font_context.deinit();
|
|
sgl.shutdown();
|
|
sg.shutdown();
|
|
}
|
|
|
|
pub fn beginFrame() void {
|
|
const zone = tracy.initZone(@src(), .{ });
|
|
defer zone.deinit();
|
|
|
|
draw_frame.init();
|
|
draw_frame.screen_size = Vec2.init(sapp.widthf(), sapp.heightf());
|
|
draw_frame.scissor_stack.appendAssumeCapacity(Rect.init(0, 0, sapp.widthf(), sapp.heightf()));
|
|
|
|
imgui.newFrame(.{
|
|
.width = @intFromFloat(draw_frame.screen_size.x),
|
|
.height = @intFromFloat(draw_frame.screen_size.y),
|
|
.delta_time = sapp.frameDuration(),
|
|
.dpi_scale = sapp.dpiScale()
|
|
});
|
|
|
|
font_context.clearState();
|
|
sgl.defaults();
|
|
sgl.matrixModeProjection();
|
|
sgl.ortho(0, draw_frame.screen_size.x, draw_frame.screen_size.y, 0, -1, 1);
|
|
sgl.loadPipeline(main_pipeline);
|
|
}
|
|
|
|
pub fn endFrame() void {
|
|
const zone = tracy.initZone(@src(), .{ });
|
|
defer zone.deinit();
|
|
|
|
assert(draw_frame.scissor_stack.items.len == 1);
|
|
|
|
var pass_action: sg.PassAction = .{};
|
|
|
|
pass_action.colors[0] = sg.ColorAttachmentAction{
|
|
.load_action = .CLEAR,
|
|
.clear_value = .{
|
|
.r = draw_frame.bg_color.x,
|
|
.g = draw_frame.bg_color.y,
|
|
.b = draw_frame.bg_color.z,
|
|
.a = draw_frame.bg_color.w
|
|
}
|
|
};
|
|
|
|
font_context.flush();
|
|
|
|
{
|
|
sg.beginPass(.{
|
|
.action = pass_action,
|
|
.swapchain = sglue.swapchain()
|
|
});
|
|
defer sg.endPass();
|
|
|
|
sgl.draw();
|
|
|
|
imgui.render();
|
|
}
|
|
sg.commit();
|
|
}
|
|
|
|
pub fn pushTransform(translation: Vec2, scale: f32) void {
|
|
sgl.pushMatrix();
|
|
sgl.translate(translation.x, translation.y, 0);
|
|
sgl.scale(scale, scale, 1);
|
|
}
|
|
|
|
pub fn popTransform() void {
|
|
sgl.popMatrix();
|
|
}
|
|
|
|
pub fn drawQuad(quad: [4]Vec2, color: Vec4) void {
|
|
sgl.beginQuads();
|
|
defer sgl.end();
|
|
|
|
sgl.c4f(color.x, color.y, color.z, color.w);
|
|
for (quad) |position| {
|
|
sgl.v2f(position.x, position.y);
|
|
}
|
|
}
|
|
|
|
pub fn drawRectangle(pos: Vec2, size: Vec2, color: Vec4) void {
|
|
drawQuad(
|
|
.{
|
|
// Top left
|
|
pos,
|
|
// Top right
|
|
pos.add(.{ .x = size.x, .y = 0 }),
|
|
// Bottom right
|
|
pos.add(size),
|
|
// Bottom left
|
|
pos.add(.{ .x = 0, .y = size.y }),
|
|
},
|
|
color
|
|
);
|
|
}
|
|
|
|
pub fn drawTriangle(tri: [3]Vec2, color: Vec4) void {
|
|
sgl.beginTriangles();
|
|
defer sgl.end();
|
|
|
|
sgl.c4f(color.x, color.y, color.z, color.w);
|
|
for (tri) |position| {
|
|
sgl.v2f(position.x, position.y);
|
|
}
|
|
}
|
|
|
|
pub fn drawLine(from: Vec2, to: Vec2, color: Vec4, width: f32) void {
|
|
const step = to.sub(from).normalized().multiplyScalar(width/2);
|
|
|
|
const top_left = from.add(step.rotateLeft90());
|
|
const bottom_left = from.add(step.rotateRight90());
|
|
const top_right = to.add(step.rotateLeft90());
|
|
const bottom_right = to.add(step.rotateRight90());
|
|
|
|
drawQuad(
|
|
.{ top_right, top_left, bottom_left, bottom_right },
|
|
color
|
|
);
|
|
}
|
|
|
|
pub fn drawRectanglOutline(pos: Vec2, size: Vec2, color: Vec4, width: f32) void {
|
|
// TODO: Don't use line segments
|
|
drawLine(pos, pos.add(.{ .x = size.x, .y = 0 }), color, width);
|
|
drawLine(pos, pos.add(.{ .x = 0, .y = size.y }), color, width);
|
|
drawLine(pos.add(.{ .x = 0, .y = size.y }), pos.add(size), color, width);
|
|
drawLine(pos.add(.{ .x = size.x, .y = 0 }), pos.add(size), color, width);
|
|
}
|
|
|
|
pub fn pushScissor(rect: Rect) void {
|
|
draw_frame.scissor_stack.appendAssumeCapacity(rect);
|
|
|
|
sgl.scissorRectf(rect.pos.x, rect.pos.y, rect.size.x, rect.size.y, true);
|
|
}
|
|
|
|
pub fn popScissor() void {
|
|
_ = draw_frame.scissor_stack.pop().?;
|
|
const rect = draw_frame.scissor_stack.getLast();
|
|
|
|
sgl.scissorRectf(rect.pos.x, rect.pos.y, rect.size.x, rect.size.y, true);
|
|
}
|
|
|
|
pub fn addFont(name: [*c]const u8, data: []const u8) !Font.Id {
|
|
return try font_context.addFont(name, data);
|
|
}
|
|
|
|
pub const DrawTextOptions = struct {
|
|
font: Font.Id,
|
|
size: f32 = 16,
|
|
color: Vec4 = rgb(255, 255, 255),
|
|
};
|
|
|
|
pub fn drawText(position: Vec2, text: []const u8, opts: DrawTextOptions) void {
|
|
pushTransform(.{ .x = 0, .y = 0}, 1/font_resolution_scale);
|
|
defer popTransform();
|
|
|
|
font_context.setFont(opts.font);
|
|
font_context.setSize(opts.size * font_resolution_scale);
|
|
font_context.setAlign(.{ .x = .left, .y = .top });
|
|
font_context.setSpacing(0);
|
|
|
|
const r: u8 = @intFromFloat(opts.color.x * 255);
|
|
const g: u8 = @intFromFloat(opts.color.y * 255);
|
|
const b: u8 = @intFromFloat(opts.color.z * 255);
|
|
const a: u8 = @intFromFloat(opts.color.w * 255);
|
|
const color: u32 = r | (@as(u32, g) << 8) | (@as(u32, b) << 16) | (@as(u32, a) << 24);
|
|
font_context.setColor(color);
|
|
font_context.drawText(
|
|
position.x * font_resolution_scale,
|
|
position.y * font_resolution_scale,
|
|
text
|
|
);
|
|
}
|