mcap/modules/imgui-dx11-win32/generate.jai

321 lines
12 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Usage: jai generate.jai - compile
// jai generate.jai - compile debug
// Builds in release mode by default.
IMGUI_PATH :: "../imgui-source"; // v1.92.9-docking (WIP)
DECLARATIONS_TO_OMIT :: string.[
// We have custom declarations for these in module.jai:
"ImVec2",
"ImVec4",
"ImColor",
"ImVector",
"ImDrawCallback",
// Jai has its own, better "String_Builder"
"ImGuiTextBuffer",
"ImDrawCallback_ResetRenderState", // casting an integer to a function pointer is not supported in Jai.
];
#run,stallable {
#if OS != .WINDOWS { #assert "Only Windows is supported for this configuration."; }
set_build_options_dc(.{do_output=false});
root_options := get_build_options();
args := root_options.compile_time_command_line;
if !generate_imgui_bindings(args) {
compiler_set_workspace_status(.FAILED);
}
}
generate_imgui_bindings :: (args: [] string) -> bool {
compile := array_find(args, "compile");
compile_debug := array_find(args, "debug");
if compile {
imgui_src_files: [..] string;
array_add(*imgui_src_files, tprint("%/imgui.cpp", IMGUI_PATH));
array_add(*imgui_src_files, tprint("%/imgui_widgets.cpp", IMGUI_PATH));
array_add(*imgui_src_files, tprint("%/imgui_draw.cpp", IMGUI_PATH));
array_add(*imgui_src_files, tprint("%/imgui_tables.cpp", IMGUI_PATH));
array_add(*imgui_src_files, tprint("%/backends/imgui_impl_dx11.cpp", IMGUI_PATH));
array_add(*imgui_src_files, tprint("%/backends/imgui_impl_win32.cpp", IMGUI_PATH));
// array_add(*imgui_src_files, tprint("%/imgui_demo.cpp", IMGUI_PATH));
success := true;
#if OS == .WINDOWS {
make_directory_if_it_does_not_exist("windows");
lib_name := ifx compile_debug
then "windows/ImGui.static.debug"
else "windows/ImGui.static.release";
// We set IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS. Otherwise all users of the ImGui module have to link against "imm32".
success &&= build_cpp_static_lib(
lib_name,
..imgui_src_files,
extra=.[
"/DIMGUI_API=__declspec(dllexport)",
"/DIMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS",
tprint("/I%", IMGUI_PATH)
// "/DIMGUI_USE_WCHAR32",
],
debug=compile_debug,
);
} else {
assert(false, "this OS is not supported, check the procedure % for details\n", #procedure_name());
}
if !success return false;
}
defer {
deinit(*enums_for_typedefs);
array_reset(*begin_end_string_args);
}
output_filename: string;
opts: Generate_Bindings_Options;
{
using opts;
#if OS == .WINDOWS {
array_add(*library_search_paths, "windows");
if (compile_debug) {
array_add(*libraries, .{filename="ImGui.static.debug"});
output_filename = "windows_debug.jai";
footer = #string EOF
#import "Windows";
imgui_static :: #library,no_dll "windows/ImGui.static.debug";
EOF
} else {
array_add(*libraries, .{filename="ImGui.static.release"});
output_filename = "windows_release.jai";
footer = #string EOF
#import "Windows";
imgui_static :: #library,no_dll "windows/ImGui.static.release";
EOF
}
generate_library_declarations = false;
}
array_add(*include_paths, IMGUI_PATH);
array_add(*include_paths, tprint("%/backends", IMGUI_PATH));
array_add(*source_files, tprint("%/imgui.h", IMGUI_PATH));
array_add(*source_files, tprint("%/backends/imgui_impl_dx11.h", IMGUI_PATH));
array_add(*source_files, tprint("%/backends/imgui_impl_win32.h", IMGUI_PATH));
array_add(*extra_clang_arguments, "-x", "c++", "-DWIN32_LEAN_AND_MEAN"/*, "-DIMGUI_USE_WCHAR32"*/);
array_add(*flatten_namespaces, "ImGui");
array_add(*strip_prefixes, "ImGui");
auto_detect_enum_prefixes = false;
log_stripped_declarations = false;
generate_compile_time_struct_checks = false;
strip_flags |= .INLINED_FUNCTIONS; // "inline" functions are not present in the static library.
visitor = imgui_visitor;
get_func_args_for_printing = imgui_get_function_arg_for_printing;
}
success := generate_bindings(opts, output_filename);
return success;
}
begin_end_string_args: [..]*Declaration;
enums_for_typedefs: Table(string, *Enum);
prefixes_to_keep: [..]string;
imgui_visitor :: (decl: *Declaration, parent_decl: *Declaration) -> Declaration_Visit_Result {
// The parent check is because if we have a struct to keep we want
// everything inside it to stay even if the names aren't to keep, and
// so we only apply this check on top-level declarations.
if prefixes_to_keep.count && !parent_decl {
found := false;
for prefixes_to_keep {
if begins_with(decl.name, it) {
found = true;
break;
}
}
if !found {
decl.decl_flags |= .OMIT_FROM_OUTPUT;
return .STOP;
}
}
// ImGui has "typedef ints" for each enum. but we want to just use the enums directly,
// so we get nice type safety, and .unary dots, etc.
//
// Look for declarations inside functions (i.e., arguments) or structs (i.e., fields)
get_associated_enum_name :: (name: string) -> string {
if name.count > 1 && name[name.count - 1] != #char "_" {
return tprint("%_", name);
}
return name;
}
if !parent_decl {
if array_find(DECLARATIONS_TO_OMIT, decl.name) {
decl.decl_flags |= .OMIT_FROM_OUTPUT;
return .STOP;
}
if decl.name == "ImGuiContext" {
decl.output_name = decl.name; // Don’t shorten ImGuiContext to Context as this would conflict
}
if decl.kind == .TYPEDEF {
old_name := decl.name;
if old_name {
new_name := get_associated_enum_name(old_name);
for context.generator.global_scope.members {
if it.kind != .ENUM || it.name != new_name continue;
en := cast(*Enum)it;
// Don't output the integer typedef
decl.decl_flags |= .OMIT_FROM_OUTPUT;
// Make sure the enums like "FocusFlags_" get outputted as "FocusFlags"
if en.output_name.count > 2 && en.output_name[en.output_name.count - 1] == #char "_" {
en.output_name.count -= 1;
}
// Keep the comment, if there is one:
if decl.comment.text && !en.comment.text {
en.comment = decl.comment;
}
table_add(*enums_for_typedefs, old_name, en);
break;
}
}
}
}
// Look for function arguments with "typedef" types.
if parent_decl &&
(parent_decl.kind == .FUNCTION || parent_decl.kind == .STRUCT) &&
decl.kind == .DECLARATION && decl.type.type_of_typedef != null
{
old_name := decl.type.type_of_typedef.name;
if old_name {
found, en := table_find(*enums_for_typedefs, old_name);
if found {
change_type_to_enum(decl, en);
}
}
}
if decl.kind == .ENUM {
// ImGui has lots of enums that should be enum flags. Luckily, they all end in "…Flags_".
if ends_with(decl.name, "Flags_") {
en := cast(*Enum)decl;
en.flags |= .IS_ENUM_FLAGS;
en.flags |= .VALUES_IN_HEX;
}
}
if decl.kind == .FUNCTION {
func := cast(*Function)decl;
type := func.type.type_of_function;
// ImGui also has functions with pairs of arguments like "text_begin" and "text_end"
// for the pointers to the beginning and end of a string. We'll generate wrappers for
// those function which take a jai string.
//
// Here we collect pointers to "_begin" arguments, and use them in
// get_func_args_for_printing below.
for type.arguments {
if it_index == 0 || !ends_with(it.name, "_end") continue;
arg_type := find_underlying_type(it.type);
if !arg_type.pointer_to || !(arg_type.pointer_to.number_flags & ._8BIT) continue; // Only convert *u8 ranges to string
name_part := slice(it.name, 0, it.name.count - "_end".count);
if !name_part continue;
// the first arg may appear as "text_begin" or just "text"
name_part_with_begin := tprint("%_begin", name_part);
prev_arg := type.arguments[it_index - 1];
if prev_arg.name == name_part || prev_arg.name == name_part_with_begin {
array_add(*begin_end_string_args, prev_arg);
func.decl_flags |= .NEEDS_ARGUMENT_WRAPPER;
}
}
}
return .RECURSE;
}
// Modify function with _begin and _end string arguments as they get printed.
// See the note above about "text_begin".
imgui_get_function_arg_for_printing :: (func: *Function, type: *Function_Type, mode: Arg_Print_Mode) -> []*Declaration {
args: [..]*Declaration;
for type.arguments {
if array_find(begin_end_string_args, it) {
if mode == {
case .OUTER_WRAPPER;
// For the outer wrapper, remove any "_begin" part of the argument, and
// change its type to a jai string
arg_copy := New(Declaration);
arg_copy.* = it.*;
arg_copy.type = context.generator.type_def_jai_string;
if ends_with(it.name, "_begin") {
arg_copy.output_name = slice(it.name, 0, it.name.count - "_begin".count);
}
array_add(*args, arg_copy);
it_index += 1; // skip the next argument-we collpase the _begin and _end args into one string arg
continue;
case .INNER_WRAPPER_CALL;
// For the inner wrapper function call, we pass the incoming arg
// as two arguments: "jai_string.data, jai_string.data + jai_string.count" -- corresponding
// to the _begin and _end parts.
base_arg_name := it.name;
if ends_with(it.name, "_begin") {
base_arg_name = slice(it.name, 0, it.name.count - "_begin".count);
}
arg_copy := New(Declaration);
arg_copy.* = it.*;
arg_copy.output_name = tprint("%.data", base_arg_name);
array_add(*args, arg_copy);
arg_copy_2 := New(Declaration);
arg_copy_2.* = it.*;
arg_copy_2.output_name = tprint("%.data + %.count", base_arg_name, base_arg_name);
array_add(*args, arg_copy_2);
it_index += 1; // skip the next arg, since we already added a second one above
continue;
}
}
array_add(*args, it);
}
return args;
}
#import "Basic";
#import "Bindings_Generator";
#import "BuildCpp";
#import "Compiler";
#import "Process";
#import "File";
#import "Hash_Table";
#import "String";
#import "Windows_Utf8";