Adds support for dear_imgui_internal.h

This commit is contained in:
Mason Remaley 2025-11-16 03:25:19 -08:00
parent 76e41b87cd
commit 8a7d77cdd4
8 changed files with 168922 additions and 136 deletions

View file

@ -44,6 +44,11 @@ Here's how to build `cimgui.h`:
python3 $DEAR_BINDINGS_PATH/dear_bindings.py -o src/cached/cimgui $DEAR_IMGUI_PATH/imgui.h
```
Here's how to build `cimgui_internal.h`:
```sh
python3 $DEAR_BINDINGS_PATH/dear_bindings.py -o src/cached/cimgui_internal --include $DEAR_IMGUI_PATH/imgui.h $DEAR_IMGUI_PATH/imgui_internal.h
```
Here's how to build a backend, using Vulkan as an example:
```sh

View file

@ -49,7 +49,10 @@ pub fn build(b: *std.Build) void {
});
dear_imgui_lib.addCSourceFiles(.{
.root = b.path("src/cached"),
.files = &.{"dcimgui.cpp"},
.files = &.{
"dcimgui.cpp",
"dcimgui_internal.cpp",
},
.flags = flags,
});
b.installArtifact(dear_imgui_lib);
@ -119,6 +122,7 @@ pub fn build(b: *std.Build) void {
const dear_imgui_zig = generate_dear_imgui.addOutputFileArg("dear_imgui.zig");
generate_dear_imgui.addFileArg(b.path("src/templates/cimgui_prefix.zig.template"));
generate_dear_imgui.addFileArg(b.path("src/templates/cimgui_postfix.zig.template"));
generate_dear_imgui.addFileArg(b.path("src/cached/dcimgui_internal.json"));
const dear_imgui_zig_module = b.addModule("dear_imgui", .{
.root_source_file = dear_imgui_zig,
.target = target,

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,7 @@
{
"defines": [],
"enums": [],
"typedefs": [],
"structs": [],
"functions": []
}

File diff suppressed because it is too large Load diff

View file

@ -8,6 +8,37 @@ const DeclarationKind = enum {
};
const Declarations = std.StringArrayHashMap(DeclarationKind);
const Symbols = struct {
public: std.StringArrayHashMap(void),
front_buf: std.StringArrayHashMap(void),
pub fn init(allocator: Allocator) @This() {
return .{
.public = .init(allocator),
.front_buf = .init(allocator),
};
}
pub fn deinit(self: *@This()) void {
self.public.deinit();
self.front_buf.deinit();
}
/// Returns true if there's already a public symbol with this name.
pub fn put(self: *@This(), name: []const u8) !bool {
const trimmed = std.mem.trimRight(u8, name, "_");
if (self.public.contains(trimmed)) return false;
try self.front_buf.put(trimmed, {});
return true;
}
pub fn makePublic(self: *@This()) void {
std.mem.swap(@FieldType(@This(), "public"), &self.public, &self.front_buf);
self.front_buf.clearRetainingCapacity();
}
};
const max_size = 5000000;
// The header type we'll parse from JSON. Fields are only included as needed.
@ -55,6 +86,7 @@ const Header = struct {
const Struct = struct {
name: []const u8,
is_internal: bool,
is_anonymous: bool,
kind: enum { @"struct", @"union" },
forward_declaration: bool,
@ -66,7 +98,7 @@ const Header = struct {
is_anonymous: bool,
type: Type,
width: ?usize = null,
default_value: ?std.json.Value = null,
default_value: ?[]const u8 = null,
conditionals: []const Conditional = &.{},
};
};
@ -155,6 +187,7 @@ pub fn main() !void {
const out_path = args.next().?;
const prefix_path = args.next();
const postfix_path = args.next();
const internal_path = args.next();
std.debug.assert(args.next() == null);
const out = try std.fs.cwd().createFile(out_path, .{});
@ -171,45 +204,36 @@ pub fn main() !void {
try writer.writeAll("\n// End of prefix\n\n");
}
// Build a list of symbols so we can skip duplicates of public symbols in the internal headers
var symbols: Symbols = .init(allocator);
defer symbols.deinit();
// Write the source
{
const source = try std.fs.cwd().readFileAlloc(allocator, in_path, max_size);
defer allocator.free(source);
const main_source = try std.fs.cwd().readFileAlloc(allocator, in_path, max_size);
defer allocator.free(main_source);
try writeSource(
allocator,
main_source,
writer,
&symbols,
false,
);
symbols.makePublic();
const header = try std.json.parseFromSlice(Header, allocator, source, .{
.ignore_unknown_fields = true,
});
defer header.deinit();
// Write the internal source, if supplied
if (internal_path) |p| {
const internal_source = try std.fs.cwd().readFileAlloc(allocator, p, max_size);
defer allocator.free(internal_source);
// We need the list of declarations up front.
var declarations = try getDeclarations(allocator, &header.value);
defer declarations.deinit();
// Write all defines as private constants.
try writeDefines(writer, &header.value);
// Write all typedefs as private constants.
try writeTypedefs(writer, &header.value, &declarations);
// Write all cimgui functions as private extern functions.
try writeExternFunctions(writer, &header.value, &declarations);
// Alias cimgui free functions under Zig friendly names.
try writeFreeFunctions(writer, &header.value);
// Get a list of cimgui methods. These were already written as externs, and can be aliased
// when we write their respective types.
var methods = try Methods.get(allocator, &header.value);
defer methods.deinit(allocator);
// Write cimgui enums as Zig enums.
try writeEnums(allocator, writer, &header.value);
// Write cimgui structs as Zig structs and unions.
try writeStructs(writer, &header.value, &declarations, &methods);
// Write helpers used by the other generated code.
try writeHelpers(writer);
try writer.writeAll("pub const internal = struct {\n");
try writeSource(
allocator,
internal_source,
writer,
&symbols,
true,
);
try writer.writeAll("};\n");
}
// Write the postfix
@ -224,12 +248,61 @@ pub fn main() !void {
try writer.flush();
}
fn writeSource(
allocator: Allocator,
source: []const u8,
writer: *std.Io.Writer,
symbols: *Symbols,
internal: bool,
) !void {
const header = try std.json.parseFromSlice(Header, allocator, source, .{
.ignore_unknown_fields = true,
});
defer header.deinit();
// We need the list of declarations up front.
var declarations = try getDeclarations(allocator, &header.value);
defer declarations.deinit();
// Write all defines as private constants.
try writeDefines(writer, internal, symbols, &header.value);
// Write all typedefs as private constants.
try writeTypedefs(writer, &header.value, symbols, &declarations);
// Write all cimgui functions as private extern functions.
try writeExternFunctions(writer, &header.value, symbols, &declarations);
// Alias cimgui free functions under Zig friendly names.
try writeFreeFunctions(writer, &header.value, symbols);
// Get a list of cimgui methods. These were already written as externs, and can be aliased
// when we write their respective types.
var methods = try Methods.get(allocator, &header.value);
defer methods.deinit(allocator);
// Write cimgui enums as Zig enums.
try writeEnums(allocator, writer, internal, &header.value, symbols);
// Write cimgui structs as Zig structs and unions.
try writeStructs(writer, &header.value, &declarations, &methods, symbols);
// Write helpers used by the other generated code. We skip this for the internal header
// since we're going to concatenate it with the main header, so this stuff will already be
// available.
if (!internal) {
try writeHelpers(writer);
}
}
fn getDeclarations(allocator: Allocator, header: *const Header) !Declarations {
var declarations = Declarations.init(allocator);
errdefer declarations.deinit();
for (header.structs) |ty| {
if (skip(ty.conditionals)) continue;
const trimmed = std.mem.trimRight(u8, ty.name, "_");
var kind: DeclarationKind = .normal;
if (ty.forward_declaration) {
if (std.mem.eql(u8, "ImDrawData", ty.name)) {
@ -259,12 +332,10 @@ fn getDeclarations(allocator: Allocator, header: *const Header) !Declarations {
};
}
const trimmed = std.mem.trimRight(u8, ty.name, "_");
try declarations.put(trimmed, kind);
}
for (header.enums) |e| {
if (e.is_internal) continue;
if (skip(e.conditionals)) continue;
const trimmed = std.mem.trimRight(u8, e.name, "_");
@ -274,26 +345,36 @@ fn getDeclarations(allocator: Allocator, header: *const Header) !Declarations {
return declarations;
}
fn writeDefines(writer: anytype, header: *const Header) !void {
fn writeDefines(writer: anytype, internal: bool, symbols: *Symbols, header: *const Header) !void {
// We skip defines from the internal namespace for now
if (internal) return;
for (header.defines) |define| {
if (define.is_internal) continue;
if (define.is_internal != internal) continue;
if (skip(define.conditionals)) continue;
if (define.content) |content| {
if (std.mem.startsWith(u8, content, "(")) {
const end = std.mem.indexOfScalar(u8, content, ')').?;
const ty = content[1..end];
const val = content[end + 1 ..];
try writer.print("const {s}: ", .{define.name});
try writeTypeName(writer, ty);
try writer.print(" = {s};\n", .{val});
} else {
try writer.print("const {s} = {s};\n", .{ define.name, content });
if (try symbols.put(define.name)) {
if (std.mem.startsWith(u8, content, "(")) {
const end = std.mem.indexOfScalar(u8, content, ')').?;
const ty = content[1..end];
const val = content[end + 1 ..];
try writer.print("const {s}: ", .{define.name});
try writeTypeName(writer, ty);
try writer.print(" = {s};\n", .{val});
} else {
try writer.print("const {s} = {s};\n", .{ define.name, content });
}
}
}
}
}
fn writeTypedefs(writer: anytype, header: *const Header, declarations: *const Declarations) !void {
fn writeTypedefs(
writer: anytype,
header: *const Header,
symbols: *Symbols,
declarations: *const Declarations,
) !void {
for (header.typedefs) |typedef| {
// Skip typedefs skipped by the preprocessor
if (skip(typedef.conditionals)) continue;
@ -306,41 +387,46 @@ fn writeTypedefs(writer: anytype, header: *const Header, declarations: *const De
std.mem.eql(u8, typedef.name, typedef.type.declaration.?)) continue;
// Write the typedef prefix
try writer.writeAll("const ");
try writeTypeName(writer, typedef.name);
try writer.writeAll(" = ");
try writeType(writer, typedef.type, declarations, .{});
try writer.writeAll(";\n");
if (try symbols.put(typedef.name)) {
try writer.writeAll("const ");
try writeTypeName(writer, typedef.name);
try writer.writeAll(" = ");
try writeType(writer, typedef.type, declarations, .{});
try writer.writeAll(";\n");
}
}
}
fn writeExternFunctions(
writer: anytype,
header: *const Header,
symbols: *Symbols,
declarations: *const Declarations,
) !void {
for (header.functions) |function| {
if (skip(function.conditionals)) continue;
if (argsContainsVaList(function.arguments)) continue;
try writer.print("extern fn {s}(", .{function.name});
for (function.arguments) |argument| {
if (argument.type) |ty| {
std.debug.assert(!argument.is_varargs);
try writeType(writer, ty, declarations, .{
.is_instance_pointer = argument.is_instance_pointer,
.is_argument = true,
.default_null = if (argument.default_value) |d| std.mem.eql(u8, d, "NULL") else false,
});
} else {
std.debug.assert(argument.is_varargs);
try writer.writeAll("...");
if (try symbols.put(function.name)) {
try writer.print("extern fn {s}(", .{function.name});
for (function.arguments) |argument| {
if (argument.type) |ty| {
std.debug.assert(!argument.is_varargs);
try writeType(writer, ty, declarations, .{
.is_instance_pointer = argument.is_instance_pointer,
.is_argument = true,
.default_null = if (argument.default_value) |d| std.mem.eql(u8, d, "NULL") else false,
});
} else {
std.debug.assert(argument.is_varargs);
try writer.writeAll("...");
}
try writer.writeAll(", ");
}
try writer.writeAll(", ");
try writer.writeAll(") callconv(.c) ");
try writeType(writer, function.return_type, declarations, .{ .is_result = true });
try writer.writeAll(";\n");
}
try writer.writeAll(") callconv(.c) ");
try writeType(writer, function.return_type, declarations, .{ .is_result = true });
try writer.writeAll(";\n");
}
}
@ -355,15 +441,17 @@ fn argsContainsVaList(arguments: []const Header.Function.Argument) bool {
return false;
}
fn writeFreeFunctions(writer: anytype, header: *const Header) !void {
fn writeFreeFunctions(writer: anytype, header: *const Header, symbols: *Symbols) !void {
for (header.functions) |function| {
if (skip(function.conditionals)) continue;
if (function.original_class != null) continue;
if (argsContainsVaList(function.arguments)) continue;
try writer.writeAll("pub const ");
try writeFunctionName(writer, function.name);
try writer.print(" = {s};\n", .{function.name});
if (try symbols.put(function.name)) {
try writer.writeAll("pub const ");
try writeFunctionName(writer, function.name);
try writer.print(" = {s};\n", .{function.name});
}
}
}
@ -406,24 +494,31 @@ const Methods = struct {
}
};
fn writeEnums(allocator: Allocator, writer: anytype, header: *const Header) !void {
fn writeEnums(
allocator: Allocator,
writer: anytype,
internal: bool,
header: *const Header,
symbols: *Symbols,
) !void {
for (header.enums) |e| {
if (e.is_internal) continue;
if (skip(e.conditionals)) continue;
try writer.writeAll("pub const ");
try writeTypeName(writer, e.name);
try writer.writeAll(" = ");
if (try symbols.put(e.name)) {
try writer.writeAll("pub const ");
try writeTypeName(writer, e.name);
try writer.writeAll(" = ");
if (e.is_flags_enum) {
try writeFlagsEnum(writer, e);
} else {
try writeNormalEnum(allocator, writer, e);
if (e.is_flags_enum) {
try writeFlagsEnum(writer, internal, e);
} else {
try writeNormalEnum(allocator, writer, internal, e);
}
}
}
}
fn writeFlagsEnum(writer: anytype, e: Header.Enum) !void {
fn writeFlagsEnum(writer: anytype, internal: bool, e: Header.Enum) !void {
const backing, const backing_bits = switch (e.storage_type.declaration) {
.int => .{ "c_int", @typeInfo(c_int).int.bits },
.ImU8 => .{ "u8", 8 },
@ -432,9 +527,9 @@ fn writeFlagsEnum(writer: anytype, e: Header.Enum) !void {
var current_offset: usize = 0;
var padding_i: usize = 0;
for (e.elements) |element| {
// Skip internal elements, and elements discarded by preprocessor
// Skip elements without matching visibility, and elements discarded by preprocessor
if (skip(element.conditionals)) continue;
if (element.is_internal) continue;
if (element.is_internal != internal) continue;
if (element.value == 0) continue;
// Calculate the offset of this element
@ -471,7 +566,7 @@ fn writeFlagsEnum(writer: anytype, e: Header.Enum) !void {
try writer.writeAll("};\n");
}
fn writeNormalEnum(allocator: Allocator, writer: anytype, e: Header.Enum) !void {
fn writeNormalEnum(allocator: Allocator, writer: anytype, internal: bool, e: Header.Enum) !void {
var values = std.AutoArrayHashMap(i64, void).init(allocator);
defer values.deinit();
@ -484,8 +579,8 @@ fn writeNormalEnum(allocator: Allocator, writer: anytype, e: Header.Enum) !void
// Write elements
for (e.elements) |element| {
// Skip internal and count
if (element.is_internal) continue;
// Skip non visible elements and count elements
if (element.is_internal != internal) continue;
if (element.is_count) continue;
if (skip(element.conditionals)) continue;
@ -517,6 +612,7 @@ fn writeStructs(
header: *const Header,
declarations: *const Declarations,
methods: *const Methods,
symbols: *Symbols,
) !void {
for (header.structs) |ty| {
// Skip structs skipped by the preprocessor. We don't skip structs marked as internal,
@ -528,56 +624,79 @@ fn writeStructs(
const decl_kind = declarations.get(ty.name).?;
if (decl_kind == .import) continue;
// Write the struct
try writer.writeAll("pub const ");
try writeTypeName(writer, ty.name);
try writer.writeAll(" = ");
if (try symbols.put(ty.name)) {
// Write the struct
try writer.writeAll("pub const ");
try writeTypeName(writer, ty.name);
try writer.writeAll(" = ");
// If we're opaque, don't try to fill out the struct fields
if (decl_kind == .@"opaque") {
try writer.writeAll("opaque {};\n");
continue;
}
// Declare the struct or union
switch (ty.kind) {
.@"struct" => try writer.writeAll("extern struct {\n"),
.@"union" => try writer.writeAll("extern union {\n"),
}
// Fill in the fields
for (ty.fields) |field| {
// Skip fields skipped by the preprocessor.
if (skip(field.conditionals)) continue;
// Not yet used, but when it is we want to start using it. Safe to disable this assert
// if you're just trying to get things working with a different version.
if (field.default_value != null) @panic("unimplemented");
// Write the field.
try writer.writeAll(" ");
if (field.is_anonymous) {
try writer.writeAll("data");
} else {
try writeFieldName(writer, field.name);
// If we're opaque, don't try to fill out the struct fields
if (decl_kind == .@"opaque") {
try writer.writeAll("opaque {};\n");
continue;
}
try writer.writeAll(": ");
try writeType(writer, field.type, declarations, .{});
try writer.writeAll(",\n");
}
// Alias all relevant methods into the type.
const method_names = methods.types.getPtr(ty.name).?;
for (method_names.items) |name| {
try writer.writeAll(" pub const ");
try writeFunctionName(writer, name[ty.name.len + 1 ..]);
try writer.print(" = {s};\n", .{name});
}
// Declare the struct or union
switch (ty.kind) {
.@"struct" => try writer.writeAll("extern struct {\n"),
.@"union" => try writer.writeAll("extern union {\n"),
}
try writer.writeAll("};\n");
// Fill in the fields
for (ty.fields) |field| {
// Skip fields skipped by the preprocessor.
if (skip(field.conditionals)) continue;
// Write the field.
try writer.writeAll(" ");
if (field.is_anonymous) {
try writer.writeAll("data");
} else {
try writeFieldName(writer, field.name);
}
try writer.writeAll(": ");
try writeType(writer, field.type, declarations, .{});
if (field.default_value) |v| {
try writer.writeAll(" = ");
try writeValue(writer, v);
}
try writer.writeAll(",\n");
}
// Alias all relevant methods into the type.
const method_names = methods.types.getPtr(ty.name).?;
for (method_names.items) |name| {
try writer.writeAll(" pub const ");
try writeFunctionName(writer, name[ty.name.len + 1 ..]);
try writer.print(" = {s};\n", .{name});
}
try writer.writeAll("};\n");
}
}
}
pub fn writeValue(writer: anytype, value: []const u8) !void {
// Strip the `f` postfix off of floats
float: {
if (value.len > 0 and
value[value.len - 1] == 'f' and
value[0] >= '0' and
value[0] <= '9')
{
switch (value[0]) {
'0'...'9' => {},
else => break :float,
}
try writer.writeAll(value[0 .. value.len - 1]);
return;
}
}
// Write everything else as is
try writer.writeAll(value);
}
fn writeHelpers(writer: anytype) !void {
try writer.writeAll(
\\fn toUsize(v: anytype) usize {
@ -836,6 +955,12 @@ fn skip(conditionals: []const Header.Conditional) bool {
if (std.mem.eql(u8, conditional.expression, "defined(VK_USE_PLATFORM_WIN32_KHR)&&!defined(NOMINMAX)")) break :b true;
if (std.mem.eql(u8, conditional.expression, "IMGUI_IMPL_VULKAN_HAS_DYNAMIC_RENDERING")) break :b true;
if (std.mem.eql(u8, conditional.expression, "defined(VK_VERSION_1_3)|| defined(VK_KHR_dynamic_rendering)")) break :b true;
if (std.mem.eql(u8, conditional.expression, "IMGUI_HAS_DOCK")) break :b true;
if (std.mem.eql(u8, conditional.expression, "IMGUI_ENABLE_SSE")) break :b true;
if (std.mem.eql(u8, conditional.expression, "IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS")) break :b true;
if (std.mem.eql(u8, conditional.expression, "IMGUI_ENABLE_STB_TRUETYPE")) break :b true;
if (std.mem.eql(u8, conditional.expression, "IMGUI_ENABLE_FREETYPE")) break :b true;
if (std.mem.eql(u8, conditional.expression, "IMGUI_ENABLE_TEST_ENGINE")) break :b true;
// False conditionals
if (std.mem.eql(u8, conditional.expression, "IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT")) break :b false;
@ -854,6 +979,9 @@ fn skip(conditionals: []const Header.Conditional) bool {
if (std.mem.eql(u8, conditional.expression, "IMGUI_HAS_IMSTR")) break :b false;
if (std.mem.eql(u8, conditional.expression, "IMGUI_DISABLE_DEBUG_TOOLS")) break :b false;
if (std.mem.eql(u8, conditional.expression, "ImTextureID_Invalid")) break :b false;
if (std.mem.eql(u8, conditional.expression, "IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS")) break :b false;
if (std.mem.eql(u8, conditional.expression, "IMGUI_DISABLE_FILE_FUNCTIONS")) break :b false;
if (std.mem.eql(u8, conditional.expression, "IMGUI_STB_NAMESPACE")) break :b false;
std.debug.panic("unexpected preprocessor conditional: {s}", .{conditional.expression});
};
@ -974,7 +1102,13 @@ fn writeFunctionName(writer: anytype, raw: []const u8) !void {
// Imgui prefixes
{
const prefixes: []const []const u8 = &.{ "cImGui_ImplVulkan", "cImGui_ImplSDL3", "ImGui", "Im" };
const prefixes: []const []const u8 = &.{
"cImGui_ImplVulkan",
"cImGui_ImplSDL3",
"ImGui",
"Im",
"cIm",
};
for (prefixes) |prefix| {
if (std.mem.startsWith(u8, name, prefix)) {
name = name[prefix.len..];