From ae235293fc4176f0e97ebc94e0f20fa3155003e6 Mon Sep 17 00:00:00 2001 From: Colin Bellino Date: Tue, 30 Jul 2024 19:27:26 +0200 Subject: [PATCH 1/2] Add Jai bindgen --- bindgen/.gitignore | 1 + bindgen/gen_all.py | 8 +- bindgen/gen_jai.py | 500 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 508 insertions(+), 1 deletion(-) create mode 100644 bindgen/gen_jai.py diff --git a/bindgen/.gitignore b/bindgen/.gitignore index 8dccea13e..926ae5250 100644 --- a/bindgen/.gitignore +++ b/bindgen/.gitignore @@ -7,3 +7,4 @@ sokol-zig/ sokol-odin/ sokol-rust/ sokol-d/ +sokol-jai/ diff --git a/bindgen/gen_all.py b/bindgen/gen_all.py index 3b29383dc..6800909af 100644 --- a/bindgen/gen_all.py +++ b/bindgen/gen_all.py @@ -1,4 +1,4 @@ -import os, gen_nim, gen_zig, gen_odin, gen_rust, gen_d +import os, gen_nim, gen_zig, gen_odin, gen_rust, gen_d, gen_jai tasks = [ [ '../sokol_log.h', 'slog_', [] ], @@ -12,6 +12,12 @@ [ '../util/sokol_shape.h', 'sshape_', ['sg_'] ], ] +# Jai +gen_jai.prepare() +for task in tasks: + [c_header_path, main_prefix, dep_prefixes] = task + gen_jai.gen(c_header_path, main_prefix, dep_prefixes) + # Odin gen_odin.prepare() for task in tasks: diff --git a/bindgen/gen_jai.py b/bindgen/gen_jai.py new file mode 100644 index 000000000..2c83775b4 --- /dev/null +++ b/bindgen/gen_jai.py @@ -0,0 +1,500 @@ +#------------------------------------------------------------------------------- +# gen_jai.py +# +# Generate Jai bindings. +#------------------------------------------------------------------------------- +import gen_ir +import gen_util as util +import os, shutil, sys + +bindings_root = 'sokol-jai' +c_root = f'{bindings_root}/sokol/c' +module_root = f'{bindings_root}/sokol' + +module_names = { + 'slog_': 'log', + 'sg_': 'gfx', + 'sapp_': 'app', + 'stm_': 'time', + 'saudio_': 'audio', + 'sgl_': 'gl', + 'sdtx_': 'debugtext', + 'sshape_': 'shape', + 'sglue_': 'glue', +} + +system_libs = { + 'sg_': { + 'windows': { + 'd3d11': '#system_library,link_always "gdi32"; #system_library,link_always "dxguid"; #system_library,link_always "user32"; #system_library,link_always "shell32"; #system_library,link_always "d3d11";', + 'gl': '#system_library,link_always "gdi32"; #system_library,link_always "dxguid"; #system_library,link_always "user32"; #system_library,link_always "shell32";', + }, + 'macos': { + 'metal': '#library "../../libclang_rt.osx"; #system_library,link_always "Cocoa"; #system_library,link_always "QuartzCore"; #system_library,link_always "Metal"; #system_library,link_always "MetalKit";', + 'gl': '#system_library,link_always "Cocoa"; #system_library,link_always "QuartzCore"; #system_library,link_always "OpenGL";', + }, + 'linux': { + 'gl': '#system_library,link_always "libXcursor"; #system_library,link_always "libX11"; #system_library,link_always "libXi"; #system_library,link_always "libGL";', + } + }, + 'sapp_': { + 'windows': { + 'd3d11': '#system_library,link_always "gdi32"; #system_library,link_always "dxguid"; #system_library,link_always "user32"; #system_library,link_always "shell32";', + 'gl': '#system_library,link_always "gdi32"; #system_library,link_always "dxguid"; #system_library,link_always "user32"; #system_library,link_always "shell32";', + }, + 'macos': { + 'metal': '#library "../../libclang_rt.osx"; #system_library,link_always "Cocoa"; #system_library,link_always "QuartzCore";', + 'gl': '#system_library,link_always "Cocoa"; #system_library,link_always "QuartzCore";', + }, + 'linux': { + 'gl': '#system_library,link_always "libXcursor"; #system_library,link_always "libX11"; #system_library,link_always "libXi"; #system_library,link_always "libGL";', + } + }, + 'saudio_': { + 'windows': { + 'd3d11': '#system_library,link_always "ole32";', + 'gl': '#system_library,link_always "ole32";', + }, + 'macos': { + 'metal': '#system_library,link_always "AudioToolbox";', + 'gl': '#system_library,link_always "AudioToolbox";', + }, + 'linux': { + 'gl': '#system_library,link_always "asound"; #system_library,link_always "dl"; #system_library,link_always "pthread";', + } + } +} + +c_source_names = { + 'slog_': 'sokol_log.c', + 'sg_': 'sokol_gfx.c', + 'sapp_': 'sokol_app.c', + 'sapp_sg': 'sokol_glue.c', + 'stm_': 'sokol_time.c', + 'saudio_': 'sokol_audio.c', + 'sgl_': 'sokol_gl.c', + 'sdtx_': 'sokol_debugtext.c', + 'sshape_': 'sokol_shape.c', + 'sglue_': 'sokol_glue.c', +} + +ignores = [ + 'sdtx_printf', + 'sdtx_vprintf', + 'sg_install_trace_hooks', + 'sg_trace_hooks', +] + +# NOTE: syntax for function results: "func_name.RESULT" +overrides = { + 'context': 'ctx', # reserved keyword + 'SGL_NO_ERROR': 'SGL_ERROR_NO_ERROR', +} + +prim_types = { + 'int': 's32', + 'bool': 'bool', + 'char': 'u8', + 'int8_t': 's8', + 'uint8_t': 'u8', + 'int16_t': 's16', + 'uint16_t': 'u16', + 'int32_t': 's32', + 'uint32_t': 'u32', + 'int64_t': 's64', + 'uint64_t': 'u64', + 'float': 'float', + 'double': 'float64', + 'uintptr_t': 'u64', + 'intptr_t': 's64', + 'size_t': 'u64' +} + +prim_defaults = { + 'int': '0', + 'bool': 'false', + 'int8_t': '0', + 'uint8_t': '0', + 'int16_t': '0', + 'uint16_t': '0', + 'int32_t': '0', + 'uint32_t': '0', + 'int64_t': '0', + 'uint64_t': '0', + 'float': '0.0', + 'double': '0.0', + 'uintptr_t': '0', + 'intptr_t': '0', + 'size_t': '0' +} + +struct_types = [] +enum_types = [] +enum_items = {} +out_lines = '' + +def reset_globals(): + global struct_types + global enum_types + global enum_items + global out_lines + struct_types = [] + enum_types = [] + enum_items = {} + out_lines = '' + +def l(s): + global out_lines + out_lines += s + '\n' + +def check_override(name, default=None): + if name in overrides: + return overrides[name] + elif default is None: + return name + else: + return default + +def check_ignore(name): + return name in ignores + +# PREFIX_BLA_BLUB to BLA_BLUB, prefix_bla_blub to bla_blub +def as_snake_case(s, prefix): + outp = s + if outp.lower().startswith(prefix): + outp = outp[len(prefix):] + return outp + +def get_jai_module_path(c_prefix): + return f'{module_root}/{module_names[c_prefix]}' + +def get_csource_path(c_prefix): + return f'{c_root}/{c_source_names[c_prefix]}' + +def make_jai_module_directory(c_prefix): + path = get_jai_module_path(c_prefix) + if not os.path.isdir(path): + os.makedirs(path) + +def as_prim_type(s): + return prim_types[s] + +def as_struct_or_enum_type(s, prefix): + return s + +# PREFIX_ENUM_BLA_BLUB => BLA_BLUB, _PREFIX_ENUM_BLA_BLUB => BLA_BLUB +def as_enum_item_name(s): + outp = s.lstrip('_') + parts = outp.split('_')[2:] + outp = '_'.join(parts) + if outp[0].isdigit(): + outp = '_' + outp + return outp + +def enum_default_item(enum_name): + return enum_items[enum_name][0] + +def is_prim_type(s): + return s in prim_types + +def is_int_type(s): + return s == "int" + +def is_struct_type(s): + return s in struct_types + +def is_enum_type(s): + return s in enum_types + +def is_const_prim_ptr(s): + for prim_type in prim_types: + if s == f"const {prim_type} *": + return True + return False + +def is_prim_ptr(s): + for prim_type in prim_types: + if s == f"{prim_type} *": + return True + return False + +def is_const_struct_ptr(s): + for struct_type in struct_types: + if s == f"const {struct_type} *": + return True + return False + +def type_default_value(s): + return prim_defaults[s] + +def map_type(type, prefix, sub_type): + if sub_type not in ['c_arg', 'struct_field']: + sys.exit(f"Error: map_type(): unknown sub_type '{sub_type}") + if type == "void": + return "" + elif is_struct_type(type): + return as_struct_or_enum_type(type, prefix) + elif is_enum_type(type): + return as_struct_or_enum_type(type, prefix) + elif util.is_void_ptr(type): + return "*void" + elif util.is_const_void_ptr(type): + return "*void" + elif util.is_string_ptr(type): + return "*u8" + elif is_const_struct_ptr(type): + return f"*{as_struct_or_enum_type(util.extract_ptr_type(type), prefix)}" + elif is_prim_ptr(type): + return f"*{as_prim_type(util.extract_ptr_type(type))}" + elif is_const_prim_ptr(type): + return f"*{as_prim_type(util.extract_ptr_type(type))}" + elif util.is_1d_array_type(type): + array_type = util.extract_array_type(type) + array_sizes = util.extract_array_sizes(type) + return f"[{array_sizes[0]}]{map_type(array_type, prefix, sub_type)}" + elif util.is_2d_array_type(type): + array_type = util.extract_array_type(type) + array_sizes = util.extract_array_sizes(type) + return f"[{array_sizes[0]}][{array_sizes[1]}]{map_type(array_type, prefix, sub_type)}" + elif util.is_func_ptr(type): + res_type = funcptr_result_c(type, prefix) + res_str = '' if res_type == '' else f' -> {res_type}' + return f'({funcptr_args_c(type, prefix)}){res_str} #c_call' + elif is_prim_type(type): + return as_prim_type(type) + else: + sys.exit(f"Error map_type(): unknown type '{type}'") + +def funcdecl_args_c(decl, prefix): + s = '' + func_name = decl['name'] + for param_decl in decl['params']: + if s != '': + s += ', ' + param_name = param_decl['name'] + param_type = check_override(f'{func_name}.{param_name}', default=param_decl['type']) + s += f"{param_name}: {map_type(param_type, prefix, 'c_arg')}" + return s + +def funcptr_args_c(field_type, prefix): + tokens = field_type[field_type.index('(*)')+4:-1].split(',') + s = '' + arg_index = 0 + for token in tokens: + arg_type = token.strip() + if s != '': + s += ', ' + c_arg = map_type(arg_type, prefix, 'c_arg') + if c_arg == '': + return '' + else: + s += f'a{arg_index}: {c_arg}' + arg_index += 1 + return s + +def funcptr_result_c(field_type, prefix): + res_type = field_type[:field_type.index('(*)')].strip() + return map_type(res_type, prefix, 'c_arg') + +def funcdecl_result_c(decl, prefix): + func_name = decl['name'] + decl_type = decl['type'] + res_c_type = decl_type[:decl_type.index('(')].strip() + return map_type(check_override(f'{func_name}.RESULT', default=res_c_type), prefix, 'c_arg') + +def get_system_libs(module, platform, backend): + if module in system_libs: + if platform in system_libs[module]: + if backend in system_libs[module][platform]: + libs = system_libs[module][platform][backend] + if libs != '': + return f"{libs}" + return '' + +def gen_c_imports(inp, c_prefix, prefix): + module_name = inp["module"] + clib_prefix = f'sokol_{module_name}' + clib_import = f'{clib_prefix}_clib' + windows_d3d11_libs = get_system_libs(prefix, 'windows', 'd3d11') + windows_gl_libs = get_system_libs(prefix, 'windows', 'gl') + macos_metal_libs = get_system_libs(prefix, 'macos', 'metal') + macos_gl_libs = get_system_libs(prefix, 'macos', 'gl') + linux_gl_libs = get_system_libs(prefix, 'linux', 'gl') + l( '#module_parameters(DEBUG := false, USE_GL := false, USE_DLL := false);') + l( '') + l( '#scope_export;') + l( '') + l( '#if OS == .WINDOWS {') + l( ' #if USE_DLL {') + l( ' #if USE_GL {') + l(f' {windows_gl_libs}') + l(f' #if DEBUG {{ {clib_import} :: #library "{clib_prefix}_windows_x64_gl_debug"; }}') + l(f' else {{ {clib_import} :: #library "{clib_prefix}_windows_x64_gl_release"; }}') + l( ' } else {') + l(f' {windows_d3d11_libs}') + l(f' #if DEBUG {{ {clib_import} :: #library "{clib_prefix}_windows_x64_d3d11_debug"; }}') + l(f' else {{ {clib_import} :: #library "{clib_prefix}_windows_x64_d3d11_release"; }}') + l( ' }') + l( ' } else {') + l( ' #if USE_GL {') + l(f' {windows_gl_libs}') + l(f' #if DEBUG {{ {clib_import} :: #library,no_dll "{clib_prefix}_windows_x64_gl_debug"; }}') + l(f' else {{ {clib_import} :: #library,no_dll "{clib_prefix}_windows_x64_gl_release"; }}') + l( ' } else {') + l(f' {windows_d3d11_libs}') + l(f' #if DEBUG {{ {clib_import} :: #library,no_dll "{clib_prefix}_windows_x64_d3d11_debug"; }}') + l(f' else {{ {clib_import} :: #library,no_dll "{clib_prefix}_windows_x64_d3d11_release"; }}') + l( ' }') + l( ' }') + l( '}') + l( 'else #if OS == .MACOS {') + l( ' #if USE_DLL {') + l(f' #if USE_GL && CPU == .ARM64 && DEBUG {{ {clib_import} :: #library "../dylib/sokol_dylib_macos_arm64_gl_debug.dylib"; }}') + l(f' else #if USE_GL && CPU == .ARM64 && !DEBUG {{ {clib_import} :: #library "../dylib/sokol_dylib_macos_arm64_gl_release.dylib"; }}') + l(f' else #if USE_GL && CPU == .X64 && DEBUG {{ {clib_import} :: #library "../dylib/sokol_dylib_macos_x64_gl_debug.dylib"; }}') + l(f' else #if USE_GL && CPU == .X64 && !DEBUG {{ {clib_import} :: #library "../dylib/sokol_dylib_macos_x64_gl_release.dylib"; }}') + l(f' else #if !USE_GL && CPU == .ARM64 && DEBUG {{ {clib_import} :: #library "../dylib/sokol_dylib_macos_arm64_metal_debug.dylib"; }}') + l(f' else #if !USE_GL && CPU == .ARM64 && !DEBUG {{ {clib_import} :: #library "../dylib/sokol_dylib_macos_arm64_metal_release.dylib"; }}') + l(f' else #if !USE_GL && CPU == .X64 && DEBUG {{ {clib_import} :: #library "../dylib/sokol_dylib_macos_x64_metal_debug.dylib"; }}') + l(f' else #if !USE_GL && CPU == .X64 && !DEBUG {{ {clib_import} :: #library "../dylib/sokol_dylib_macos_x64_metal_release.dylib"; }}') + l( ' } else {') + l( ' #if USE_GL {') + l(f' {macos_gl_libs}') + l( ' #if CPU == .ARM64 {') + l(f' #if DEBUG {{ {clib_import} :: #library,no_dll "{clib_prefix}_macos_arm64_gl_debug"; }}') + l(f' else {{ {clib_import} :: #library,no_dll "{clib_prefix}_macos_arm64_gl_release"; }}') + l( ' } else {') + l(f' #if DEBUG {{ {clib_import} :: #library,no_dll "{clib_prefix}_macos_x64_gl_debug"; }}') + l(f' else {{ {clib_import} :: #library,no_dll "{clib_prefix}_macos_x64_gl_release"; }}') + l( ' }') + l( ' } else {') + l(f' {macos_metal_libs}') + l( ' #if CPU == .ARM64 {') + l(f' #if DEBUG {{ {clib_import} :: #library,no_dll "{clib_prefix}_macos_arm64_metal_debug"; }}') + l(f' else {{ {clib_import} :: #library,no_dll "{clib_prefix}_macos_arm64_metal_release"; }}') + l( ' } else {') + l(f' #if DEBUG {{ {clib_import} :: #library,no_dll "{clib_prefix}_macos_x64_metal_debug"; }}') + l(f' else {{ {clib_import} :: #library,no_dll "{clib_prefix}_macos_x64_metal_release"; }}') + l( ' }') + l( ' }') + l( ' }') + l( '} else #if OS == .LINUX {') + if linux_gl_libs: + l(f' {linux_gl_libs}') + l(f' #if DEBUG {{ {clib_import} :: #library,no_dll "{clib_prefix}_linux_x64_gl_debug"; }}') + l(f' else {{ {clib_import} :: #library,no_dll "{clib_prefix}_linux_x64_gl_release"; }}') + l( '} else {') + l( ' log_error("This OS is currently not supported");') + l( '}') + l( '') + + prefix = inp['prefix'] + for decl in inp['decls']: + if decl['kind'] == 'func' and not decl['is_dep'] and not check_ignore(decl['name']): + args = funcdecl_args_c(decl, prefix) + res_type = funcdecl_result_c(decl, prefix) + res_str = '-> void' if res_type == '' else f'-> {res_type}' + l(f"{decl['name']} :: ({args}) {res_str} #foreign {clib_import};") + l('') + +def gen_consts(decl, prefix): + for item in decl['items']: + item_name = check_override(item['name']) + l(f"{as_snake_case(item_name, prefix)} :: {item['value']};") + l('') + +def gen_struct(decl, prefix): + c_struct_name = check_override(decl['name']) + struct_name = as_struct_or_enum_type(c_struct_name, prefix) + l(f'{struct_name} :: struct {{') + for field in decl['fields']: + field_name = check_override(field['name']) + field_type = map_type(check_override(f'{c_struct_name}.{field_name}', default=field['type']), prefix, 'struct_field') + # any field name starting with _ is considered private + if field_name.startswith('_'): + l(f' _ : {field_type};') + else: + l(f' {field_name} : {field_type};') + l('}') + l('') + +def gen_enum(decl, prefix): + enum_name = check_override(decl['name']) + l(f'{as_struct_or_enum_type(enum_name, prefix)} :: enum u32 {{') + for item in decl['items']: + item_name = as_enum_item_name(check_override(item['name'])) + if item_name != 'FORCE_U32' and item_name != 'NUM': + if 'value' in item: + l(f" {item_name} :: {item['value']};") + else: + l(f" {item_name};") + l('}') + l('') + +def gen_imports(dep_prefixes): + for dep_prefix in dep_prefixes: + dep_module_name = module_names[dep_prefix] + l(f'#import,dir "../{dep_module_name}"(DEBUG = USE_DLL, USE_GL = USE_DLL, USE_DLL = USE_DLL);') + l('') + +def gen_helpers(inp): + if inp['prefix'] == 'sdtx_': + l('sdtx_printf :: (s: string, args: ..Any) {') + l(' #import "Basic";') + l(' fstr := tprint(s, ..args);') + l(' sdtx_putr(to_c_string(fstr), xx fstr.count);') + l('}') + +def gen_module(inp, c_prefix, dep_prefixes): + pre_parse(inp) + l('// machine generated, do not edit') + gen_imports(dep_prefixes) + gen_helpers(inp) + prefix = inp['prefix'] + gen_c_imports(inp, c_prefix, prefix) + for decl in inp['decls']: + if not decl['is_dep']: + kind = decl['kind'] + if kind == 'consts': + gen_consts(decl, prefix) + elif not check_ignore(decl['name']): + if kind == 'struct': + gen_struct(decl, prefix) + elif kind == 'enum': + gen_enum(decl, prefix) + +def pre_parse(inp): + global struct_types + global enum_types + for decl in inp['decls']: + kind = decl['kind'] + if kind == 'struct': + struct_types.append(decl['name']) + elif kind == 'enum': + enum_name = decl['name'] + enum_types.append(enum_name) + enum_items[enum_name] = [] + for item in decl['items']: + enum_items[enum_name].append(as_enum_item_name(item['name'])) + +def prepare(): + print('=== Generating Jai bindings:') + if not os.path.isdir(module_root): + os.makedirs(module_root) + if not os.path.isdir(c_root): + os.makedirs(c_root) + +def gen(c_header_path, c_prefix, dep_c_prefixes): + if not c_prefix in module_names: + print(f' >> warning: skipping generation for {c_prefix} prefix...') + return + reset_globals() + make_jai_module_directory(c_prefix) + print(f' {c_header_path} => {module_names[c_prefix]}') + shutil.copyfile(c_header_path, f'{c_root}/{os.path.basename(c_header_path)}') + csource_path = get_csource_path(c_prefix) + module_name = module_names[c_prefix] + ir = gen_ir.gen(c_header_path, csource_path, module_name, c_prefix, dep_c_prefixes) + gen_module(ir, c_prefix, dep_c_prefixes) + with open(f"{module_root}/{ir['module']}/module.jai", 'w', newline='\n') as f_outp: + f_outp.write(out_lines) From f146fc11dd328b33802f52df8671cf7a9f8b8a07 Mon Sep 17 00:00:00 2001 From: Colin Bellino Date: Sat, 10 Aug 2024 16:17:24 +0200 Subject: [PATCH 2/2] Add line to README so gen_all.py works with Jai --- bindgen/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/bindgen/README.md b/bindgen/README.md index 40e5c417e..a73155217 100644 --- a/bindgen/README.md +++ b/bindgen/README.md @@ -27,6 +27,7 @@ To update the Zig bindings: > git clone https://github.com/floooh/sokol-odin > git clone https://github.com/floooh/sokol-rust > git clone https://github.com/floooh/sokol-d +> git clone https://github.com/colinbellino/sokol-jai > python3 gen_all.py ```