imgui 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +273 -0
- data/Rakefile +97 -0
- data/api/v1.json +53 -0
- data/ext/CMakeLists.txt +128 -0
- data/ext/Rakefile +31 -0
- data/ext/backend_function_bridge.cpp +180 -0
- data/ext/backend_function_bridge.h +40 -0
- data/ext/build_cimgui.rb +59 -0
- data/ext/extconf.rb +25 -0
- data/ext/glfw_vulkan_bridge.cpp +14 -0
- data/ext/imgui_ruby_build_config.h +17 -0
- data/ext/imgui_ruby_glfw.cpp +73 -0
- data/ext/imgui_ruby_sdl3.cpp +70 -0
- data/ext/imgui_ruby_wgpu.cpp +75 -0
- data/ext/install_cimgui.rb +25 -0
- data/ext/webgpu_function_bridge.cpp +245 -0
- data/generator/api_emitter.rb +164 -0
- data/generator/emitter.rb +317 -0
- data/generator/generate.rb +47 -0
- data/generator/native-dependencies.yml +88 -0
- data/generator/native_dependency_lock.rb +78 -0
- data/generator/native_dependency_snapshot.rb +159 -0
- data/generator/overrides.yml +11 -0
- data/generator/type_mapper.rb +112 -0
- data/generator/update_vendor.rb +30 -0
- data/lib/imgui/api.rb +88 -0
- data/lib/imgui/api_generated.rb +1633 -0
- data/lib/imgui/api_support.rb +144 -0
- data/lib/imgui/backends/glfw.rb +75 -0
- data/lib/imgui/backends/opengl3.rb +44 -0
- data/lib/imgui/backends/sdl3.rb +133 -0
- data/lib/imgui/backends/wgpu.rb +176 -0
- data/lib/imgui/backends.rb +60 -0
- data/lib/imgui/draw_data.rb +43 -0
- data/lib/imgui/dsl.rb +311 -0
- data/lib/imgui/dsl_support.rb +107 -0
- data/lib/imgui/easy_loop.rb +25 -0
- data/lib/imgui/errors.rb +11 -0
- data/lib/imgui/fonts.rb +46 -0
- data/lib/imgui/io.rb +91 -0
- data/lib/imgui/layout.rb +138 -0
- data/lib/imgui/memory_pool.rb +18 -0
- data/lib/imgui/native/enums.rb +2251 -0
- data/lib/imgui/native/functions.rb +1470 -0
- data/lib/imgui/native/structs.rb +1926 -0
- data/lib/imgui/native/typedefs.rb +93 -0
- data/lib/imgui/native.rb +130 -0
- data/lib/imgui/plot/api.rb +273 -0
- data/lib/imgui/plot/native/enums.rb +593 -0
- data/lib/imgui/plot/native/functions.rb +749 -0
- data/lib/imgui/plot/native/structs.rb +363 -0
- data/lib/imgui/plot/native/typedefs.rb +79 -0
- data/lib/imgui/plot.rb +13 -0
- data/lib/imgui/struct_value.rb +33 -0
- data/lib/imgui/style.rb +44 -0
- data/lib/imgui/value.rb +104 -0
- data/lib/imgui/version.rb +5 -0
- data/lib/imgui/widgets.rb +187 -0
- data/lib/imgui.rb +30 -0
- data/rakelib/platform_gem.rake +54 -0
- data/rakelib/source_gem.rake +75 -0
- metadata +129 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ImGui
|
|
4
|
+
class << self
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
def guarded_native_call(function, *arguments)
|
|
8
|
+
guard_context!
|
|
9
|
+
Native.public_send(function, *arguments)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def define_generated_method(ruby_name, native_name, argument_types, defaults)
|
|
13
|
+
singleton_class.define_method(ruby_name) do |*arguments, **keywords|
|
|
14
|
+
if arguments.length > argument_types.length
|
|
15
|
+
raise ArgumentError, "wrong number of arguments (given #{arguments.length}, expected #{argument_types.length})"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
values = argument_types.each_with_index.map do |(name, c_type), index|
|
|
19
|
+
value = if index < arguments.length
|
|
20
|
+
arguments[index]
|
|
21
|
+
elsif keywords.key?(name.to_sym)
|
|
22
|
+
keywords.delete(name.to_sym)
|
|
23
|
+
elsif defaults.key?(name)
|
|
24
|
+
defaults.fetch(name)
|
|
25
|
+
else
|
|
26
|
+
raise ArgumentError, "missing argument: #{name}"
|
|
27
|
+
end
|
|
28
|
+
convert_generated_argument(c_type, value)
|
|
29
|
+
end
|
|
30
|
+
raise ArgumentError, "unknown keywords: #{keywords.keys.join(", ")}" unless keywords.empty?
|
|
31
|
+
|
|
32
|
+
guarded_native_call(native_name, *values)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def convert_generated_argument(c_type, value)
|
|
37
|
+
type = c_type.gsub(/\bconst\b/, "").strip
|
|
38
|
+
return StructValue.vec2(value) if type == "ImVec2"
|
|
39
|
+
return StructValue.vec4(value) if type == "ImVec4"
|
|
40
|
+
return !!value if type == "bool"
|
|
41
|
+
return Float(value) if %w[float double].include?(type)
|
|
42
|
+
return Integer(value) if type.match?(/\A(?:unsigned )?(?:char|short|int|long|long long)\z/)
|
|
43
|
+
return value&.to_s if type == "char*"
|
|
44
|
+
|
|
45
|
+
value
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def guard_context!
|
|
49
|
+
context = Native.igGetCurrentContext
|
|
50
|
+
raise NoContextError, "create an ImGui context before calling this method" if null_pointer?(context)
|
|
51
|
+
|
|
52
|
+
key = context_key(context)
|
|
53
|
+
context_threads[key] ||= Thread.current
|
|
54
|
+
check_context_thread!(context)
|
|
55
|
+
context
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def check_context_thread!(context)
|
|
59
|
+
return unless thread_checks_enabled?
|
|
60
|
+
|
|
61
|
+
owner = context_threads[context_key(context)]
|
|
62
|
+
return unless owner && owner != Thread.current
|
|
63
|
+
|
|
64
|
+
raise ThreadError, "ImGui context belongs to thread #{owner.object_id}, not #{Thread.current.object_id}"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def null_pointer?(pointer)
|
|
68
|
+
pointer.nil? || pointer == 0 || pointer.respond_to?(:null?) && pointer.null?
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def context_key(context)
|
|
72
|
+
context.respond_to?(:address) ? context.address : context.object_id
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def context_threads
|
|
76
|
+
@context_threads ||= {}
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def io_views
|
|
80
|
+
@io_views ||= {}
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def style_views
|
|
84
|
+
@style_views ||= {}
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def clear_context_views(context)
|
|
88
|
+
key = context_key(context)
|
|
89
|
+
io_views.delete(key)
|
|
90
|
+
style_views.delete(key)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def edit_scalar(function, label, value, type:, arguments:)
|
|
94
|
+
guard_context!
|
|
95
|
+
if value.is_a?(Value)
|
|
96
|
+
require_value_type!(value, type)
|
|
97
|
+
return Native.public_send(function, label, value.pointer, *arguments)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
pointer = MemoryPool.pointer(type)
|
|
101
|
+
pointer.put(type, 0, coerce_scalar(type, value))
|
|
102
|
+
changed = Native.public_send(function, label, pointer, *arguments)
|
|
103
|
+
[changed, pointer.get(type, 0)]
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def edit_vector(function, label, value, count:, arguments:)
|
|
107
|
+
guard_context!
|
|
108
|
+
expected_type = "vec#{count}".to_sym
|
|
109
|
+
if value.is_a?(Value)
|
|
110
|
+
require_value_type!(value, expected_type)
|
|
111
|
+
return Native.public_send(function, label, value.pointer, *arguments)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
values = Array(value)
|
|
115
|
+
raise ArgumentError, "expected #{count} values" unless values.length == count
|
|
116
|
+
|
|
117
|
+
pointer = MemoryPool.pointer(:float, count)
|
|
118
|
+
pointer.write_array_of_float(values.map { |item| Float(item) })
|
|
119
|
+
changed = Native.public_send(function, label, pointer, *arguments)
|
|
120
|
+
[changed, pointer.read_array_of_float(count)]
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def vector_result(function, *arguments, count: 2)
|
|
124
|
+
guard_context!
|
|
125
|
+
pointer = MemoryPool.pointer(:float, count)
|
|
126
|
+
Native.public_send(function, pointer, *arguments)
|
|
127
|
+
pointer.read_array_of_float(count)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def require_value_type!(value, expected)
|
|
131
|
+
return if value.type == expected
|
|
132
|
+
|
|
133
|
+
raise TypeError, "expected ImGui::Value.#{expected}, got #{value.type}"
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def coerce_scalar(type, value)
|
|
137
|
+
case type
|
|
138
|
+
when :bool then !!value
|
|
139
|
+
when :int then Integer(value)
|
|
140
|
+
else Float(value)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ImGui
|
|
4
|
+
module Backends
|
|
5
|
+
module Glfw
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def init_for_opengl(window, install_callbacks: true)
|
|
9
|
+
prepare_runtime!
|
|
10
|
+
Backends.invoke(
|
|
11
|
+
"GLFW",
|
|
12
|
+
:ImGui_ImplGlfw_InitForOpenGL,
|
|
13
|
+
Backends.pointer(window),
|
|
14
|
+
!!install_callbacks
|
|
15
|
+
)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def init_for_vulkan(window, install_callbacks: true)
|
|
19
|
+
prepare_runtime!
|
|
20
|
+
Backends.invoke(
|
|
21
|
+
"GLFW",
|
|
22
|
+
:ImGui_ImplGlfw_InitForVulkan,
|
|
23
|
+
Backends.pointer(window),
|
|
24
|
+
!!install_callbacks
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def init_for_other(window, install_callbacks: true)
|
|
29
|
+
prepare_runtime!
|
|
30
|
+
Backends.invoke(
|
|
31
|
+
"GLFW",
|
|
32
|
+
:ImGui_ImplGlfw_InitForOther,
|
|
33
|
+
Backends.pointer(window),
|
|
34
|
+
!!install_callbacks
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def install_callbacks(window)
|
|
39
|
+
Backends.invoke("GLFW", :ImGui_ImplGlfw_InstallCallbacks, Backends.pointer(window))
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def restore_callbacks(window)
|
|
43
|
+
Backends.invoke("GLFW", :ImGui_ImplGlfw_RestoreCallbacks, Backends.pointer(window))
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def chain_callbacks_for_all_windows=(enabled)
|
|
47
|
+
Backends.invoke("GLFW", :ImGui_ImplGlfw_SetCallbacksChainForAllWindows, !!enabled)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def prepare_runtime!
|
|
51
|
+
candidates = [ENV["IMGUI_RUBY_GLFW_LIB"]]
|
|
52
|
+
candidates.concat(::GLFW::API.ffi_libraries.map(&:name)) if defined?(::GLFW::API)
|
|
53
|
+
candidates.concat(
|
|
54
|
+
if FFI::Platform.windows?
|
|
55
|
+
%w[glfw3.dll glfw3_64.dll]
|
|
56
|
+
elsif FFI::Platform.mac?
|
|
57
|
+
%w[libglfw.3.dylib libglfw.dylib /opt/homebrew/lib/libglfw.3.dylib /usr/local/lib/libglfw.3.dylib]
|
|
58
|
+
else
|
|
59
|
+
%w[libglfw.so.3 libglfw.so]
|
|
60
|
+
end
|
|
61
|
+
)
|
|
62
|
+
Backends.load_runtime_library("GLFW", candidates)
|
|
63
|
+
end
|
|
64
|
+
private_class_method :prepare_runtime!
|
|
65
|
+
|
|
66
|
+
def new_frame
|
|
67
|
+
Backends.invoke("GLFW", :ImGui_ImplGlfw_NewFrame)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def shutdown
|
|
71
|
+
Backends.invoke("GLFW", :ImGui_ImplGlfw_Shutdown)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ImGui
|
|
4
|
+
module Backends
|
|
5
|
+
module OpenGL3
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def init(glsl_version = nil)
|
|
9
|
+
Backends.invoke("OpenGL3", :ImGui_ImplOpenGL3_Init, glsl_version)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def new_frame
|
|
13
|
+
Backends.invoke("OpenGL3", :ImGui_ImplOpenGL3_NewFrame)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def render_draw_data(draw_data = ImGui.draw_data)
|
|
17
|
+
return if draw_data.nil?
|
|
18
|
+
|
|
19
|
+
pointer = draw_data.respond_to?(:pointer) ? draw_data.pointer : Backends.pointer(draw_data)
|
|
20
|
+
Backends.invoke("OpenGL3", :ImGui_ImplOpenGL3_RenderDrawData, pointer)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def create_device_objects
|
|
24
|
+
Backends.invoke("OpenGL3", :ImGui_ImplOpenGL3_CreateDeviceObjects)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def destroy_device_objects
|
|
28
|
+
Backends.invoke("OpenGL3", :ImGui_ImplOpenGL3_DestroyDeviceObjects)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def create_fonts_texture
|
|
32
|
+
Backends.invoke("OpenGL3", :ImGui_ImplOpenGL3_CreateFontsTexture)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def destroy_fonts_texture
|
|
36
|
+
Backends.invoke("OpenGL3", :ImGui_ImplOpenGL3_DestroyFontsTexture)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def shutdown
|
|
40
|
+
Backends.invoke("OpenGL3", :ImGui_ImplOpenGL3_Shutdown)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ImGui
|
|
4
|
+
module Backends
|
|
5
|
+
module SDL3
|
|
6
|
+
module GamepadMode
|
|
7
|
+
AutoFirst = 0
|
|
8
|
+
AutoAll = 1
|
|
9
|
+
Manual = 2
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
def init_for_opengl(window, context)
|
|
15
|
+
prepare_runtime!
|
|
16
|
+
Backends.invoke(
|
|
17
|
+
"SDL3",
|
|
18
|
+
:ImGui_ImplSDL3_InitForOpenGL,
|
|
19
|
+
Backends.pointer(window),
|
|
20
|
+
Backends.pointer(context)
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def init_for_other(window)
|
|
25
|
+
prepare_runtime!
|
|
26
|
+
Backends.invoke("SDL3", :ImGui_ImplSDL3_InitForOther, Backends.pointer(window))
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def init_for_vulkan(window)
|
|
30
|
+
prepare_runtime!
|
|
31
|
+
Backends.invoke("SDL3", :ImGui_ImplSDL3_InitForVulkan, Backends.pointer(window))
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def init_for_d3d(window)
|
|
35
|
+
prepare_runtime!
|
|
36
|
+
Backends.invoke("SDL3", :ImGui_ImplSDL3_InitForD3D, Backends.pointer(window))
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def init_for_metal(window)
|
|
40
|
+
prepare_runtime!
|
|
41
|
+
Backends.invoke("SDL3", :ImGui_ImplSDL3_InitForMetal, Backends.pointer(window))
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def init_for_sdl_renderer(window, renderer)
|
|
45
|
+
prepare_runtime!
|
|
46
|
+
Backends.invoke(
|
|
47
|
+
"SDL3",
|
|
48
|
+
:ImGui_ImplSDL3_InitForSDLRenderer,
|
|
49
|
+
Backends.pointer(window),
|
|
50
|
+
Backends.pointer(renderer)
|
|
51
|
+
)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def init_for_sdl_gpu(window)
|
|
55
|
+
prepare_runtime!
|
|
56
|
+
Backends.invoke("SDL3", :ImGui_ImplSDL3_InitForSDLGPU, Backends.pointer(window))
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def process_event(event)
|
|
60
|
+
Backends.invoke("SDL3", :ImGui_ImplSDL3_ProcessEvent, Backends.pointer(event))
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def new_frame
|
|
64
|
+
Backends.invoke("SDL3", :ImGui_ImplSDL3_NewFrame)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def gamepad_mode=(mode)
|
|
68
|
+
set_gamepad_mode(mode)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def set_gamepad_mode(mode, gamepads: nil)
|
|
72
|
+
native_mode = normalize_gamepad_mode(mode)
|
|
73
|
+
pointers = Array(gamepads).map { |gamepad| Backends.pointer(gamepad) }
|
|
74
|
+
if native_mode == GamepadMode::Manual
|
|
75
|
+
@manual_gamepads = if pointers.empty?
|
|
76
|
+
FFI::Pointer::NULL
|
|
77
|
+
else
|
|
78
|
+
FFI::MemoryPointer.new(:pointer, pointers.length).tap do |memory|
|
|
79
|
+
memory.write_array_of_pointer(pointers)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
count = pointers.length
|
|
83
|
+
else
|
|
84
|
+
@manual_gamepads = nil
|
|
85
|
+
count = -1
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
Backends.invoke(
|
|
89
|
+
"SDL3",
|
|
90
|
+
:ImGui_ImplSDL3_SetGamepadMode,
|
|
91
|
+
native_mode,
|
|
92
|
+
@manual_gamepads || FFI::Pointer::NULL,
|
|
93
|
+
count
|
|
94
|
+
)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def shutdown
|
|
98
|
+
Backends.invoke("SDL3", :ImGui_ImplSDL3_Shutdown)
|
|
99
|
+
ensure
|
|
100
|
+
@manual_gamepads = nil
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def normalize_gamepad_mode(mode)
|
|
104
|
+
return Integer(mode) unless mode.is_a?(Symbol) || mode.is_a?(String)
|
|
105
|
+
|
|
106
|
+
{
|
|
107
|
+
auto_first: GamepadMode::AutoFirst,
|
|
108
|
+
auto_all: GamepadMode::AutoAll,
|
|
109
|
+
manual: GamepadMode::Manual
|
|
110
|
+
}.fetch(mode.to_sym) do
|
|
111
|
+
raise ArgumentError, "unknown SDL3 gamepad mode: #{mode.inspect}"
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
private_class_method :normalize_gamepad_mode
|
|
115
|
+
|
|
116
|
+
def prepare_runtime!
|
|
117
|
+
candidates = [ENV["IMGUI_RUBY_SDL3_LIB"]]
|
|
118
|
+
candidates.concat(::SDL3::Raw.ffi_libraries.map(&:name)) if defined?(::SDL3::Raw)
|
|
119
|
+
candidates.concat(
|
|
120
|
+
if FFI::Platform.windows?
|
|
121
|
+
%w[SDL3.dll]
|
|
122
|
+
elsif FFI::Platform.mac?
|
|
123
|
+
%w[libSDL3.dylib SDL3.framework/SDL3 /opt/homebrew/lib/libSDL3.dylib /usr/local/lib/libSDL3.dylib]
|
|
124
|
+
else
|
|
125
|
+
%w[libSDL3.so.0 libSDL3.so]
|
|
126
|
+
end
|
|
127
|
+
)
|
|
128
|
+
Backends.load_runtime_library("SDL3", candidates)
|
|
129
|
+
end
|
|
130
|
+
private_class_method :prepare_runtime!
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ImGui
|
|
4
|
+
module Native
|
|
5
|
+
attach_function :imgui_ruby_wgpu_required_function_count, [], :size_t
|
|
6
|
+
attach_function :imgui_ruby_wgpu_required_function_name, [:size_t], :string
|
|
7
|
+
attach_function :imgui_ruby_wgpu_set_function, [:string, :pointer], :bool
|
|
8
|
+
attach_function :imgui_ruby_wgpu_bridge_ready, [], :bool
|
|
9
|
+
attach_function :imgui_ruby_wgpu_bridge_error, [], :string
|
|
10
|
+
attach_function :imgui_ruby_wgpu_init, [:pointer, :int, :int, :int, :uint, :uint, :bool], :bool
|
|
11
|
+
attach_function :imgui_ruby_wgpu_shutdown, [], :void
|
|
12
|
+
attach_function :imgui_ruby_wgpu_new_frame, [], :void
|
|
13
|
+
attach_function :imgui_ruby_wgpu_render_draw_data, [:pointer, :pointer], :void
|
|
14
|
+
attach_function :imgui_ruby_wgpu_render_draw_data_to_view, [:pointer, :pointer, :pointer], :bool
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
module Backends
|
|
18
|
+
module WGPU
|
|
19
|
+
UNDEFINED_TEXTURE_FORMAT = 0
|
|
20
|
+
DEFAULT_SAMPLE_MASK = 0xffff_ffff
|
|
21
|
+
|
|
22
|
+
module_function
|
|
23
|
+
|
|
24
|
+
def init(
|
|
25
|
+
device:,
|
|
26
|
+
queue: nil,
|
|
27
|
+
render_target_format:,
|
|
28
|
+
depth_format: nil,
|
|
29
|
+
frames_in_flight: 3,
|
|
30
|
+
sample_count: 1,
|
|
31
|
+
sample_mask: DEFAULT_SAMPLE_MASK,
|
|
32
|
+
alpha_to_coverage: false,
|
|
33
|
+
function_table: nil,
|
|
34
|
+
library_path: nil
|
|
35
|
+
)
|
|
36
|
+
validate_init_options!(frames_in_flight, sample_count)
|
|
37
|
+
source = resolve_function_source(device, function_table, library_path)
|
|
38
|
+
configure_function_table!(source)
|
|
39
|
+
|
|
40
|
+
# The official backend obtains the default queue from the device. Accepting
|
|
41
|
+
# queue keeps the stagecraft integration signature stable and validates it
|
|
42
|
+
# when supplied, without retaining an unnecessary second queue reference.
|
|
43
|
+
Backends.pointer(queue) if queue
|
|
44
|
+
Backends.invoke(
|
|
45
|
+
"WGPU",
|
|
46
|
+
:imgui_ruby_wgpu_init,
|
|
47
|
+
Backends.pointer(device),
|
|
48
|
+
Integer(frames_in_flight),
|
|
49
|
+
Integer(render_target_format),
|
|
50
|
+
depth_format.nil? ? UNDEFINED_TEXTURE_FORMAT : Integer(depth_format),
|
|
51
|
+
Integer(sample_count),
|
|
52
|
+
Integer(sample_mask),
|
|
53
|
+
!!alpha_to_coverage
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def new_frame
|
|
58
|
+
Backends.invoke("WGPU", :imgui_ruby_wgpu_new_frame)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def render_draw_data(draw_data, encoder, target_view = nil)
|
|
62
|
+
return if draw_data.nil?
|
|
63
|
+
|
|
64
|
+
data_pointer = draw_data.respond_to?(:pointer) ? draw_data.pointer : Backends.pointer(draw_data)
|
|
65
|
+
encoder_pointer = Backends.pointer(encoder)
|
|
66
|
+
return Backends.invoke(
|
|
67
|
+
"WGPU",
|
|
68
|
+
:imgui_ruby_wgpu_render_draw_data,
|
|
69
|
+
data_pointer,
|
|
70
|
+
encoder_pointer
|
|
71
|
+
) unless target_view
|
|
72
|
+
|
|
73
|
+
Backends.invoke(
|
|
74
|
+
"WGPU",
|
|
75
|
+
:imgui_ruby_wgpu_render_draw_data_to_view,
|
|
76
|
+
data_pointer,
|
|
77
|
+
encoder_pointer,
|
|
78
|
+
Backends.pointer(target_view)
|
|
79
|
+
)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def shutdown
|
|
83
|
+
Backends.invoke("WGPU", :imgui_ruby_wgpu_shutdown)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def available?
|
|
87
|
+
Native.load!
|
|
88
|
+
Native.imgui_ruby_wgpu_bridge_error
|
|
89
|
+
true
|
|
90
|
+
rescue LibraryLoadError, MissingSymbolError
|
|
91
|
+
false
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def resolve_function_source(device, explicit, library_path)
|
|
95
|
+
return explicit if explicit
|
|
96
|
+
|
|
97
|
+
return device.function_table if device.respond_to?(:function_table)
|
|
98
|
+
return device.native_library if device.respond_to?(:native_library)
|
|
99
|
+
|
|
100
|
+
if library_path
|
|
101
|
+
library = FFI::DynamicLibrary.open(
|
|
102
|
+
File.expand_path(library_path),
|
|
103
|
+
FFI::DynamicLibrary::RTLD_LAZY | FFI::DynamicLibrary::RTLD_LOCAL
|
|
104
|
+
)
|
|
105
|
+
retained_libraries << library
|
|
106
|
+
return library
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
if defined?(::WGPU::Native) && ::WGPU::Native.respond_to?(:ffi_libraries)
|
|
110
|
+
library = ::WGPU::Native.ffi_libraries.find { |candidate| function_pointer(candidate, "wgpuDeviceCreateBuffer") }
|
|
111
|
+
return library if library
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
raise BackendUnavailableError, <<~MESSAGE.strip
|
|
115
|
+
WGPU needs a native function table. Load the wgpu/stagecraft runtime
|
|
116
|
+
first, pass function_table:, or pass library_path: to its native library.
|
|
117
|
+
MESSAGE
|
|
118
|
+
end
|
|
119
|
+
private_class_method :resolve_function_source
|
|
120
|
+
|
|
121
|
+
def configure_function_table!(source)
|
|
122
|
+
required_function_names.each do |name|
|
|
123
|
+
pointer = function_pointer(source, name)
|
|
124
|
+
raise BackendUnavailableError, "WebGPU function is unavailable: #{name}" unless pointer
|
|
125
|
+
|
|
126
|
+
configured = Backends.invoke("WGPU", :imgui_ruby_wgpu_set_function, name, pointer)
|
|
127
|
+
next if configured
|
|
128
|
+
|
|
129
|
+
raise BackendUnavailableError, "WGPU function-table bridge rejected #{name}"
|
|
130
|
+
end
|
|
131
|
+
return true if Backends.invoke("WGPU", :imgui_ruby_wgpu_bridge_ready)
|
|
132
|
+
|
|
133
|
+
raise BackendUnavailableError, "WGPU function-table bridge failed: #{Native.imgui_ruby_wgpu_bridge_error}"
|
|
134
|
+
end
|
|
135
|
+
private_class_method :configure_function_table!
|
|
136
|
+
|
|
137
|
+
def required_function_names
|
|
138
|
+
count = Backends.invoke("WGPU", :imgui_ruby_wgpu_required_function_count)
|
|
139
|
+
Array.new(count) do |index|
|
|
140
|
+
Backends.invoke("WGPU", :imgui_ruby_wgpu_required_function_name, index)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
private_class_method :required_function_names
|
|
144
|
+
|
|
145
|
+
def function_pointer(source, name)
|
|
146
|
+
value = if source.respond_to?(:fetch)
|
|
147
|
+
source.fetch(name) { source.fetch(name.to_sym, nil) }
|
|
148
|
+
elsif source.respond_to?(:find_function)
|
|
149
|
+
source.find_function(name)
|
|
150
|
+
elsif source.respond_to?(:function_address)
|
|
151
|
+
source.function_address(name)
|
|
152
|
+
elsif source.respond_to?(name)
|
|
153
|
+
source.method(name)
|
|
154
|
+
end
|
|
155
|
+
return if value.nil?
|
|
156
|
+
return FFI::Pointer.new(value.address) if value.respond_to?(:address)
|
|
157
|
+
|
|
158
|
+
Backends.pointer(value)
|
|
159
|
+
rescue FFI::NotFoundError
|
|
160
|
+
nil
|
|
161
|
+
end
|
|
162
|
+
private_class_method :function_pointer
|
|
163
|
+
|
|
164
|
+
def retained_libraries
|
|
165
|
+
@retained_libraries ||= []
|
|
166
|
+
end
|
|
167
|
+
private_class_method :retained_libraries
|
|
168
|
+
|
|
169
|
+
def validate_init_options!(frames_in_flight, sample_count)
|
|
170
|
+
raise ArgumentError, "frames_in_flight must be positive" unless Integer(frames_in_flight).positive?
|
|
171
|
+
raise ArgumentError, "sample_count must be positive" unless Integer(sample_count).positive?
|
|
172
|
+
end
|
|
173
|
+
private_class_method :validate_init_options!
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ImGui
|
|
4
|
+
module Native
|
|
5
|
+
attach_function :imgui_ruby_backend_load_library, %i[string string], :bool
|
|
6
|
+
attach_function :imgui_ruby_backend_library_ready, [:string], :bool
|
|
7
|
+
attach_function :imgui_ruby_backend_has_function, %i[string string], :bool
|
|
8
|
+
attach_function :imgui_ruby_backend_required_function_count, [:string], :size_t
|
|
9
|
+
attach_function :imgui_ruby_backend_required_function_name, %i[string size_t], :string
|
|
10
|
+
attach_function :imgui_ruby_backend_library_error, [], :string
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
module Backends
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
def pointer(value)
|
|
17
|
+
candidate = value.respond_to?(:handle) ? value.handle : value
|
|
18
|
+
candidate = candidate.to_ptr if candidate.respond_to?(:to_ptr)
|
|
19
|
+
return candidate if candidate.is_a?(FFI::Pointer)
|
|
20
|
+
return FFI::Pointer.new(candidate) if candidate.is_a?(Integer)
|
|
21
|
+
|
|
22
|
+
raise TypeError, "expected a native pointer or an object exposing #handle/#to_ptr"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def invoke(backend, function, *arguments)
|
|
26
|
+
ImGui.__send__(:guard_context!)
|
|
27
|
+
Native.public_send(function, *arguments)
|
|
28
|
+
rescue MissingSymbolError => error
|
|
29
|
+
raise BackendUnavailableError, "#{backend} backend is not included in the loaded native library: #{error.message}"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def load_runtime_library(backend, candidates)
|
|
33
|
+
ImGui.__send__(:guard_context!)
|
|
34
|
+
unless Native.imgui_ruby_backend_library_ready(backend)
|
|
35
|
+
Array(candidates).compact.uniq.each do |path|
|
|
36
|
+
break if Native.imgui_ruby_backend_load_library(backend, path.to_s)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
unless Native.imgui_ruby_backend_library_ready(backend)
|
|
41
|
+
detail = Native.imgui_ruby_backend_library_error
|
|
42
|
+
raise BackendUnavailableError,
|
|
43
|
+
"unable to load the #{backend} runtime library#{detail.to_s.empty? ? "" : ": #{detail}"}"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
count = Native.imgui_ruby_backend_required_function_count(backend)
|
|
47
|
+
missing = count.times.filter_map do |index|
|
|
48
|
+
name = Native.imgui_ruby_backend_required_function_name(backend, index)
|
|
49
|
+
name unless Native.imgui_ruby_backend_has_function(backend, name)
|
|
50
|
+
end
|
|
51
|
+
return true if missing.empty?
|
|
52
|
+
|
|
53
|
+
raise BackendUnavailableError,
|
|
54
|
+
"#{backend} runtime library is incompatible; missing: #{missing.join(", ")}"
|
|
55
|
+
rescue MissingSymbolError => error
|
|
56
|
+
raise BackendUnavailableError,
|
|
57
|
+
"#{backend} runtime loading is not included in the loaded native library: #{error.message}"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ImGui
|
|
4
|
+
class DrawData
|
|
5
|
+
attr_reader :pointer
|
|
6
|
+
|
|
7
|
+
def initialize(pointer)
|
|
8
|
+
@pointer = pointer
|
|
9
|
+
@native = Native::ImDrawData.new(pointer)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def valid?
|
|
13
|
+
@native[:Valid]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def command_lists_count
|
|
17
|
+
@native[:CmdListsCount]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def total_index_count
|
|
21
|
+
@native[:TotalIdxCount]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def total_vertex_count
|
|
25
|
+
@native[:TotalVtxCount]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
alias total_idx_count total_index_count
|
|
29
|
+
alias total_vtx_count total_vertex_count
|
|
30
|
+
|
|
31
|
+
def display_pos
|
|
32
|
+
StructValue.to_a(@native[:DisplayPos], 2)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def display_size
|
|
36
|
+
StructValue.to_a(@native[:DisplaySize], 2)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def framebuffer_scale
|
|
40
|
+
StructValue.to_a(@native[:FramebufferScale], 2)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|