rbgl_cocoa_bridge 0.1.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/CHANGELOG.md +7 -0
- data/LICENSE +21 -0
- data/README.md +162 -0
- data/Rakefile +18 -0
- data/ext/rbgl_cocoa_bridge/extconf.rb +6 -0
- data/ext/rbgl_cocoa_bridge/rbgl_cocoa_bridge.m +566 -0
- data/lib/rbgl_cocoa_bridge/version.rb +7 -0
- data/lib/rbgl_cocoa_bridge.rb +40 -0
- data/rbgl_cocoa_bridge.gemspec +28 -0
- metadata +53 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 3ab4e46975fed72e539fffd29b860aeeba042a168256a0cb85abde601f79b7fc
|
|
4
|
+
data.tar.gz: ac539f4286672ed86f2d93fe62d6327cd271f4d320b2b0e54eccb7905ec5b06e
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: aa4c3b42c4234476db6421e3fed2cdbd1c182b9d2257d4a8c321c7247af6c56a6e262beb2b921be7acb9f1aaa996e9bd6a0cdad66075e2c3de5ae93193501db4
|
|
7
|
+
data.tar.gz: 7b18283f5a33fc0ac58e2c247691500e8223a099f5721d3fdcaa3848e1e62dc5549eb611303f7c85b6fd4184a86694127f51aa8bea2643f40021e7a78755562d
|
data/CHANGELOG.md
ADDED
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Yudai Takada
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# rbgl_cocoa_bridge
|
|
2
|
+
|
|
3
|
+
A Ruby C extension providing native macOS window management and Metal GPU acceleration for graphics applications.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- macOS
|
|
8
|
+
- Ruby 3.1+
|
|
9
|
+
- Xcode Command Line Tools
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
Add this line to your application's Gemfile:
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
gem "rbgl_cocoa_bridge"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
And then execute:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
bundle install
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Or install it yourself as:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
gem install rbgl_cocoa_bridge
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
### Basic Window
|
|
34
|
+
|
|
35
|
+
```ruby
|
|
36
|
+
require "rbgl_cocoa_bridge"
|
|
37
|
+
|
|
38
|
+
# Initialize Cocoa
|
|
39
|
+
RBGL::CocoaBridge.init
|
|
40
|
+
|
|
41
|
+
# Create a window
|
|
42
|
+
handle = RBGL::CocoaBridge.window_create(800, 600, "My Window")
|
|
43
|
+
|
|
44
|
+
# Main loop
|
|
45
|
+
until RBGL::CocoaBridge.should_close?(handle)
|
|
46
|
+
# Create pixel buffer (RGBA format)
|
|
47
|
+
width, height = 800, 600
|
|
48
|
+
buffer = "\xFF\x00\x00\xFF" * (width * height) # Red
|
|
49
|
+
|
|
50
|
+
# Update pixels and present
|
|
51
|
+
RBGL::CocoaBridge.set_pixels(handle, buffer, width, height)
|
|
52
|
+
RBGL::CocoaBridge.present(handle)
|
|
53
|
+
|
|
54
|
+
# Handle events
|
|
55
|
+
events = RBGL::CocoaBridge.poll_events(handle)
|
|
56
|
+
events.each do |event|
|
|
57
|
+
case event[:type]
|
|
58
|
+
when :key_press
|
|
59
|
+
puts "Key pressed: #{event[:key]}"
|
|
60
|
+
when :mouse_press
|
|
61
|
+
puts "Mouse clicked at: #{event[:x]}, #{event[:y]}"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Cleanup
|
|
67
|
+
RBGL::CocoaBridge.window_destroy(handle)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Metal Compute Shaders
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
require "rbgl_cocoa_bridge"
|
|
74
|
+
|
|
75
|
+
RBGL::CocoaBridge.init
|
|
76
|
+
handle = RBGL::CocoaBridge.window_create(800, 600, "Compute Shader")
|
|
77
|
+
|
|
78
|
+
# Check Metal availability
|
|
79
|
+
if RBGL::CocoaBridge.metal_compute_available?(handle)
|
|
80
|
+
# Compile shader (MSL)
|
|
81
|
+
shader = <<~MSL
|
|
82
|
+
#include <metal_stdlib>
|
|
83
|
+
using namespace metal;
|
|
84
|
+
|
|
85
|
+
kernel void compute_shader(
|
|
86
|
+
texture2d<float, access::write> output [[texture(0)]],
|
|
87
|
+
constant float4 &uniforms [[buffer(0)]],
|
|
88
|
+
uint2 gid [[thread_position_in_grid]])
|
|
89
|
+
{
|
|
90
|
+
float2 uv = float2(gid) / float2(output.get_width(), output.get_height());
|
|
91
|
+
output.write(float4(uv.x, uv.y, uniforms.x, 1.0), gid);
|
|
92
|
+
}
|
|
93
|
+
MSL
|
|
94
|
+
|
|
95
|
+
RBGL::CocoaBridge.compile_compute_shader(handle, shader)
|
|
96
|
+
|
|
97
|
+
until RBGL::CocoaBridge.should_close?(handle)
|
|
98
|
+
# Dispatch with uniforms
|
|
99
|
+
time = Time.now.to_f
|
|
100
|
+
uniforms = [Math.sin(time) * 0.5 + 0.5, 0.0, 0.0, 1.0].pack("f4")
|
|
101
|
+
RBGL::CocoaBridge.dispatch_compute(handle, uniforms)
|
|
102
|
+
RBGL::CocoaBridge.present_compute(handle)
|
|
103
|
+
RBGL::CocoaBridge.poll_events(handle)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
RBGL::CocoaBridge.window_destroy(handle)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## API Reference
|
|
111
|
+
|
|
112
|
+
### Window Management
|
|
113
|
+
|
|
114
|
+
| Method | Description |
|
|
115
|
+
|--------|-------------|
|
|
116
|
+
| `init` | Initialize Cocoa application |
|
|
117
|
+
| `window_create(width, height, title)` | Create a new window, returns handle |
|
|
118
|
+
| `window_destroy(handle)` | Destroy the window |
|
|
119
|
+
| `should_close?(handle)` | Check if window should close |
|
|
120
|
+
| `poll_events(handle)` | Poll and return pending events |
|
|
121
|
+
|
|
122
|
+
### Rendering
|
|
123
|
+
|
|
124
|
+
| Method | Description |
|
|
125
|
+
|--------|-------------|
|
|
126
|
+
| `set_pixels(handle, buffer, width, height)` | Set pixel data (RGBA format) |
|
|
127
|
+
| `present(handle)` | Present the frame |
|
|
128
|
+
|
|
129
|
+
### Compute Shaders
|
|
130
|
+
|
|
131
|
+
| Method | Description |
|
|
132
|
+
|--------|-------------|
|
|
133
|
+
| `metal_compute_available?(handle)` | Check if Metal compute is available |
|
|
134
|
+
| `compile_compute_shader(handle, msl_source)` | Compile MSL compute shader |
|
|
135
|
+
| `dispatch_compute(handle, uniforms)` | Execute compute shader |
|
|
136
|
+
| `present_compute(handle)` | Present compute shader output |
|
|
137
|
+
| `has_compute_shader?(handle)` | Check if shader is compiled |
|
|
138
|
+
|
|
139
|
+
### Event Types
|
|
140
|
+
|
|
141
|
+
- `:key_press` - Key pressed (`:key`, `:char`)
|
|
142
|
+
- `:key_release` - Key released (`:key`)
|
|
143
|
+
- `:mouse_press` - Mouse button pressed (`:x`, `:y`, `:button`)
|
|
144
|
+
- `:mouse_release` - Mouse button released (`:x`, `:y`, `:button`)
|
|
145
|
+
- `:mouse_move` - Mouse moved (`:x`, `:y`)
|
|
146
|
+
|
|
147
|
+
## Development
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
# Install dependencies
|
|
151
|
+
bundle install
|
|
152
|
+
|
|
153
|
+
# Compile native extension
|
|
154
|
+
bundle exec rake compile
|
|
155
|
+
|
|
156
|
+
# Run tests
|
|
157
|
+
bundle exec rake test
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## License
|
|
161
|
+
|
|
162
|
+
The gem is available as open source under the terms of the [MIT License](LICENSE).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/gem_tasks"
|
|
4
|
+
require "rake/extensiontask"
|
|
5
|
+
require "rake/testtask"
|
|
6
|
+
|
|
7
|
+
Rake::ExtensionTask.new("rbgl_cocoa_bridge") do |ext|
|
|
8
|
+
ext.lib_dir = "lib/rbgl_cocoa_bridge"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
Rake::TestTask.new(:test) do |t|
|
|
12
|
+
t.libs << "test"
|
|
13
|
+
t.libs << "lib"
|
|
14
|
+
t.test_files = FileList["test/**/test_*.rb"]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
task test: :compile
|
|
18
|
+
task default: :test
|
|
@@ -0,0 +1,566 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* RBGL Cocoa Bridge - Native macOS window support with Metal acceleration
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
#import <Cocoa/Cocoa.h>
|
|
6
|
+
#import <Metal/Metal.h>
|
|
7
|
+
#import <QuartzCore/CAMetalLayer.h>
|
|
8
|
+
#import <ruby.h>
|
|
9
|
+
|
|
10
|
+
@interface RBGLMetalView : NSView
|
|
11
|
+
@property (nonatomic, strong) CAMetalLayer *metalLayer;
|
|
12
|
+
@property (nonatomic, strong) id<MTLDevice> device;
|
|
13
|
+
@property (nonatomic, strong) id<MTLCommandQueue> commandQueue;
|
|
14
|
+
@property (nonatomic, strong) id<MTLTexture> texture;
|
|
15
|
+
@property (nonatomic, assign) int texWidth;
|
|
16
|
+
@property (nonatomic, assign) int texHeight;
|
|
17
|
+
// Compute shader support
|
|
18
|
+
@property (nonatomic, strong) id<MTLComputePipelineState> computePipeline;
|
|
19
|
+
@property (nonatomic, strong) id<MTLBuffer> uniformBuffer;
|
|
20
|
+
@property (nonatomic, strong) id<MTLTexture> outputTexture;
|
|
21
|
+
@property (nonatomic, assign) BOOL hasComputeShader;
|
|
22
|
+
@end
|
|
23
|
+
|
|
24
|
+
@implementation RBGLMetalView
|
|
25
|
+
|
|
26
|
+
- (void)cleanup {
|
|
27
|
+
// Only cleanup if we have resources
|
|
28
|
+
if (!self.device) return;
|
|
29
|
+
|
|
30
|
+
// Wait for GPU to finish before releasing resources
|
|
31
|
+
if (self.commandQueue) {
|
|
32
|
+
@try {
|
|
33
|
+
id<MTLCommandBuffer> syncBuffer = [self.commandQueue commandBuffer];
|
|
34
|
+
if (syncBuffer) {
|
|
35
|
+
[syncBuffer commit];
|
|
36
|
+
[syncBuffer waitUntilCompleted];
|
|
37
|
+
}
|
|
38
|
+
} @catch (NSException *e) {
|
|
39
|
+
// Ignore exceptions during cleanup
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Clear all Metal resources
|
|
44
|
+
self.computePipeline = nil;
|
|
45
|
+
self.uniformBuffer = nil;
|
|
46
|
+
self.outputTexture = nil;
|
|
47
|
+
self.texture = nil;
|
|
48
|
+
self.commandQueue = nil;
|
|
49
|
+
self.metalLayer = nil;
|
|
50
|
+
self.device = nil;
|
|
51
|
+
self.hasComputeShader = NO;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// dealloc is handled automatically by ARC - no manual cleanup needed
|
|
55
|
+
// cocoa_window_destroy handles synchronization with GPU before release
|
|
56
|
+
|
|
57
|
+
- (instancetype)initWithFrame:(NSRect)frame device:(id<MTLDevice>)device width:(int)w height:(int)h {
|
|
58
|
+
self = [super initWithFrame:frame];
|
|
59
|
+
if (self) {
|
|
60
|
+
self.device = device;
|
|
61
|
+
self.texWidth = w;
|
|
62
|
+
self.texHeight = h;
|
|
63
|
+
|
|
64
|
+
self.wantsLayer = YES;
|
|
65
|
+
self.metalLayer = [CAMetalLayer layer];
|
|
66
|
+
self.metalLayer.device = device;
|
|
67
|
+
self.metalLayer.pixelFormat = MTLPixelFormatBGRA8Unorm;
|
|
68
|
+
self.metalLayer.framebufferOnly = NO;
|
|
69
|
+
self.metalLayer.displaySyncEnabled = NO; // Disable VSync for max FPS
|
|
70
|
+
self.metalLayer.frame = frame;
|
|
71
|
+
self.metalLayer.drawableSize = CGSizeMake(w, h);
|
|
72
|
+
self.layer = self.metalLayer;
|
|
73
|
+
|
|
74
|
+
self.commandQueue = [device newCommandQueue];
|
|
75
|
+
|
|
76
|
+
// Create texture for pixel data
|
|
77
|
+
MTLTextureDescriptor *texDesc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm
|
|
78
|
+
width:w
|
|
79
|
+
height:h
|
|
80
|
+
mipmapped:NO];
|
|
81
|
+
texDesc.usage = MTLTextureUsageShaderRead;
|
|
82
|
+
self.texture = [device newTextureWithDescriptor:texDesc];
|
|
83
|
+
}
|
|
84
|
+
return self;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
- (void)updatePixels:(const uint8_t *)data {
|
|
88
|
+
MTLRegion region = MTLRegionMake2D(0, 0, self.texWidth, self.texHeight);
|
|
89
|
+
[self.texture replaceRegion:region mipmapLevel:0 withBytes:data bytesPerRow:self.texWidth * 4];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
- (void)present {
|
|
93
|
+
id<CAMetalDrawable> drawable = [self.metalLayer nextDrawable];
|
|
94
|
+
if (!drawable) return;
|
|
95
|
+
|
|
96
|
+
id<MTLCommandBuffer> commandBuffer = [self.commandQueue commandBuffer];
|
|
97
|
+
|
|
98
|
+
// Blit texture to drawable
|
|
99
|
+
id<MTLBlitCommandEncoder> blitEncoder = [commandBuffer blitCommandEncoder];
|
|
100
|
+
|
|
101
|
+
[blitEncoder copyFromTexture:self.texture
|
|
102
|
+
sourceSlice:0
|
|
103
|
+
sourceLevel:0
|
|
104
|
+
sourceOrigin:MTLOriginMake(0, 0, 0)
|
|
105
|
+
sourceSize:MTLSizeMake(self.texWidth, self.texHeight, 1)
|
|
106
|
+
toTexture:drawable.texture
|
|
107
|
+
destinationSlice:0
|
|
108
|
+
destinationLevel:0
|
|
109
|
+
destinationOrigin:MTLOriginMake(0, 0, 0)];
|
|
110
|
+
|
|
111
|
+
[blitEncoder endEncoding];
|
|
112
|
+
|
|
113
|
+
[commandBuffer presentDrawable:drawable];
|
|
114
|
+
[commandBuffer commit];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
#pragma mark - Compute Shader Support
|
|
118
|
+
|
|
119
|
+
- (BOOL)compileComputeShader:(NSString *)mslSource error:(NSError **)error {
|
|
120
|
+
// Compile MSL source to library
|
|
121
|
+
id<MTLLibrary> library = [self.device newLibraryWithSource:mslSource
|
|
122
|
+
options:nil
|
|
123
|
+
error:error];
|
|
124
|
+
if (!library) return NO;
|
|
125
|
+
|
|
126
|
+
// Get compute function
|
|
127
|
+
id<MTLFunction> computeFunc = [library newFunctionWithName:@"compute_shader"];
|
|
128
|
+
if (!computeFunc) {
|
|
129
|
+
if (error) *error = [NSError errorWithDomain:@"RBGL" code:1
|
|
130
|
+
userInfo:@{NSLocalizedDescriptionKey: @"compute_shader function not found"}];
|
|
131
|
+
return NO;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Create compute pipeline
|
|
135
|
+
self.computePipeline = [self.device newComputePipelineStateWithFunction:computeFunc error:error];
|
|
136
|
+
if (!self.computePipeline) return NO;
|
|
137
|
+
|
|
138
|
+
// Create output texture (writable)
|
|
139
|
+
MTLTextureDescriptor *texDesc = [MTLTextureDescriptor
|
|
140
|
+
texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm
|
|
141
|
+
width:self.texWidth
|
|
142
|
+
height:self.texHeight
|
|
143
|
+
mipmapped:NO];
|
|
144
|
+
texDesc.usage = MTLTextureUsageShaderWrite | MTLTextureUsageShaderRead;
|
|
145
|
+
self.outputTexture = [self.device newTextureWithDescriptor:texDesc];
|
|
146
|
+
|
|
147
|
+
// Create uniform buffer (256 bytes should be enough for most shaders)
|
|
148
|
+
self.uniformBuffer = [self.device newBufferWithLength:256
|
|
149
|
+
options:MTLResourceStorageModeShared];
|
|
150
|
+
|
|
151
|
+
self.hasComputeShader = YES;
|
|
152
|
+
return YES;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
- (void)dispatchComputeWithUniforms:(const void *)uniformData length:(NSUInteger)length {
|
|
156
|
+
if (!self.hasComputeShader) return;
|
|
157
|
+
|
|
158
|
+
// Update uniform buffer
|
|
159
|
+
memcpy(self.uniformBuffer.contents, uniformData, MIN(length, 256));
|
|
160
|
+
|
|
161
|
+
id<MTLCommandBuffer> commandBuffer = [self.commandQueue commandBuffer];
|
|
162
|
+
id<MTLComputeCommandEncoder> computeEncoder = [commandBuffer computeCommandEncoder];
|
|
163
|
+
|
|
164
|
+
[computeEncoder setComputePipelineState:self.computePipeline];
|
|
165
|
+
[computeEncoder setTexture:self.outputTexture atIndex:0];
|
|
166
|
+
[computeEncoder setBuffer:self.uniformBuffer offset:0 atIndex:0];
|
|
167
|
+
|
|
168
|
+
// Calculate optimal thread group size
|
|
169
|
+
NSUInteger threadGroupSize = self.computePipeline.maxTotalThreadsPerThreadgroup;
|
|
170
|
+
NSUInteger threadGroupWidth = 16;
|
|
171
|
+
NSUInteger threadGroupHeight = 16;
|
|
172
|
+
|
|
173
|
+
if (threadGroupWidth * threadGroupHeight > threadGroupSize) {
|
|
174
|
+
threadGroupWidth = 8;
|
|
175
|
+
threadGroupHeight = 8;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
MTLSize threadsPerGroup = MTLSizeMake(threadGroupWidth, threadGroupHeight, 1);
|
|
179
|
+
MTLSize threadGroups = MTLSizeMake(
|
|
180
|
+
(self.texWidth + threadGroupWidth - 1) / threadGroupWidth,
|
|
181
|
+
(self.texHeight + threadGroupHeight - 1) / threadGroupHeight,
|
|
182
|
+
1
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
[computeEncoder dispatchThreadgroups:threadGroups threadsPerThreadgroup:threadsPerGroup];
|
|
186
|
+
[computeEncoder endEncoding];
|
|
187
|
+
|
|
188
|
+
[commandBuffer commit];
|
|
189
|
+
[commandBuffer waitUntilCompleted];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
- (void)presentCompute {
|
|
193
|
+
if (!self.hasComputeShader) return;
|
|
194
|
+
|
|
195
|
+
id<CAMetalDrawable> drawable = [self.metalLayer nextDrawable];
|
|
196
|
+
if (!drawable) return;
|
|
197
|
+
|
|
198
|
+
id<MTLCommandBuffer> commandBuffer = [self.commandQueue commandBuffer];
|
|
199
|
+
id<MTLBlitCommandEncoder> blitEncoder = [commandBuffer blitCommandEncoder];
|
|
200
|
+
|
|
201
|
+
// Blit from compute output to drawable
|
|
202
|
+
[blitEncoder copyFromTexture:self.outputTexture
|
|
203
|
+
sourceSlice:0
|
|
204
|
+
sourceLevel:0
|
|
205
|
+
sourceOrigin:MTLOriginMake(0, 0, 0)
|
|
206
|
+
sourceSize:MTLSizeMake(self.texWidth, self.texHeight, 1)
|
|
207
|
+
toTexture:drawable.texture
|
|
208
|
+
destinationSlice:0
|
|
209
|
+
destinationLevel:0
|
|
210
|
+
destinationOrigin:MTLOriginMake(0, 0, 0)];
|
|
211
|
+
|
|
212
|
+
[blitEncoder endEncoding];
|
|
213
|
+
[commandBuffer presentDrawable:drawable];
|
|
214
|
+
[commandBuffer commit];
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
@end
|
|
218
|
+
|
|
219
|
+
@interface RBGLWindow : NSWindow
|
|
220
|
+
@property (nonatomic, assign) BOOL shouldClose;
|
|
221
|
+
@property (nonatomic, strong) NSMutableArray *pendingEvents;
|
|
222
|
+
@property (nonatomic, strong) RBGLMetalView *metalView;
|
|
223
|
+
@property (nonatomic, assign) BOOL useMetal;
|
|
224
|
+
// Fallback for non-Metal
|
|
225
|
+
@property (nonatomic, strong) NSBitmapImageRep *bitmapRep;
|
|
226
|
+
@property (nonatomic, strong) NSImageView *imageView;
|
|
227
|
+
@end
|
|
228
|
+
|
|
229
|
+
@implementation RBGLWindow
|
|
230
|
+
|
|
231
|
+
- (instancetype)initWithWidth:(int)width height:(int)height title:(NSString *)title {
|
|
232
|
+
NSRect frame = NSMakeRect(100, 100, width, height);
|
|
233
|
+
self = [super initWithContentRect:frame
|
|
234
|
+
styleMask:(NSWindowStyleMaskTitled |
|
|
235
|
+
NSWindowStyleMaskClosable |
|
|
236
|
+
NSWindowStyleMaskMiniaturizable)
|
|
237
|
+
backing:NSBackingStoreBuffered
|
|
238
|
+
defer:NO];
|
|
239
|
+
if (self) {
|
|
240
|
+
self.shouldClose = NO;
|
|
241
|
+
self.pendingEvents = [NSMutableArray array];
|
|
242
|
+
[self setTitle:title];
|
|
243
|
+
[self setDelegate:(id<NSWindowDelegate>)self];
|
|
244
|
+
|
|
245
|
+
// Try to use Metal
|
|
246
|
+
id<MTLDevice> device = MTLCreateSystemDefaultDevice();
|
|
247
|
+
if (device) {
|
|
248
|
+
self.useMetal = YES;
|
|
249
|
+
self.metalView = [[RBGLMetalView alloc] initWithFrame:NSMakeRect(0, 0, width, height)
|
|
250
|
+
device:device
|
|
251
|
+
width:width
|
|
252
|
+
height:height];
|
|
253
|
+
[self setContentView:self.metalView];
|
|
254
|
+
} else {
|
|
255
|
+
// Fallback to bitmap
|
|
256
|
+
self.useMetal = NO;
|
|
257
|
+
self.bitmapRep = [[NSBitmapImageRep alloc]
|
|
258
|
+
initWithBitmapDataPlanes:NULL
|
|
259
|
+
pixelsWide:width
|
|
260
|
+
pixelsHigh:height
|
|
261
|
+
bitsPerSample:8
|
|
262
|
+
samplesPerPixel:4
|
|
263
|
+
hasAlpha:YES
|
|
264
|
+
isPlanar:NO
|
|
265
|
+
colorSpaceName:NSDeviceRGBColorSpace
|
|
266
|
+
bytesPerRow:width * 4
|
|
267
|
+
bitsPerPixel:32];
|
|
268
|
+
|
|
269
|
+
NSImage *image = [[NSImage alloc] initWithSize:NSMakeSize(width, height)];
|
|
270
|
+
[image addRepresentation:self.bitmapRep];
|
|
271
|
+
|
|
272
|
+
self.imageView = [[NSImageView alloc] initWithFrame:NSMakeRect(0, 0, width, height)];
|
|
273
|
+
[self.imageView setImage:image];
|
|
274
|
+
[self.imageView setImageScaling:NSImageScaleAxesIndependently];
|
|
275
|
+
[self setContentView:self.imageView];
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
[self setAcceptsMouseMovedEvents:YES];
|
|
279
|
+
[self makeKeyAndOrderFront:nil];
|
|
280
|
+
}
|
|
281
|
+
return self;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
- (BOOL)windowShouldClose:(id)sender {
|
|
285
|
+
self.shouldClose = YES;
|
|
286
|
+
return NO;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
- (void)keyDown:(NSEvent *)event {
|
|
290
|
+
NSDictionary *eventDict = @{
|
|
291
|
+
@"type": @"key_press",
|
|
292
|
+
@"key": @([event keyCode]),
|
|
293
|
+
@"char": [event characters] ?: @""
|
|
294
|
+
};
|
|
295
|
+
[self.pendingEvents addObject:eventDict];
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
- (void)keyUp:(NSEvent *)event {
|
|
299
|
+
NSDictionary *eventDict = @{
|
|
300
|
+
@"type": @"key_release",
|
|
301
|
+
@"key": @([event keyCode])
|
|
302
|
+
};
|
|
303
|
+
[self.pendingEvents addObject:eventDict];
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
- (void)mouseDown:(NSEvent *)event {
|
|
307
|
+
NSPoint loc = [event locationInWindow];
|
|
308
|
+
NSDictionary *eventDict = @{
|
|
309
|
+
@"type": @"mouse_press",
|
|
310
|
+
@"x": @(loc.x),
|
|
311
|
+
@"y": @(loc.y),
|
|
312
|
+
@"button": @([event buttonNumber])
|
|
313
|
+
};
|
|
314
|
+
[self.pendingEvents addObject:eventDict];
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
- (void)mouseUp:(NSEvent *)event {
|
|
318
|
+
NSPoint loc = [event locationInWindow];
|
|
319
|
+
NSDictionary *eventDict = @{
|
|
320
|
+
@"type": @"mouse_release",
|
|
321
|
+
@"x": @(loc.x),
|
|
322
|
+
@"y": @(loc.y),
|
|
323
|
+
@"button": @([event buttonNumber])
|
|
324
|
+
};
|
|
325
|
+
[self.pendingEvents addObject:eventDict];
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
- (void)mouseMoved:(NSEvent *)event {
|
|
329
|
+
NSPoint loc = [event locationInWindow];
|
|
330
|
+
NSDictionary *eventDict = @{
|
|
331
|
+
@"type": @"mouse_move",
|
|
332
|
+
@"x": @(loc.x),
|
|
333
|
+
@"y": @(loc.y)
|
|
334
|
+
};
|
|
335
|
+
[self.pendingEvents addObject:eventDict];
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
- (BOOL)canBecomeKeyWindow { return YES; }
|
|
339
|
+
- (BOOL)acceptsFirstResponder { return YES; }
|
|
340
|
+
@end
|
|
341
|
+
|
|
342
|
+
static VALUE cocoa_init(VALUE self) {
|
|
343
|
+
@autoreleasepool {
|
|
344
|
+
[NSApplication sharedApplication];
|
|
345
|
+
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
|
346
|
+
[NSApp activateIgnoringOtherApps:YES];
|
|
347
|
+
}
|
|
348
|
+
return Qnil;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
static VALUE cocoa_window_create(VALUE self, VALUE width, VALUE height, VALUE title) {
|
|
352
|
+
@autoreleasepool {
|
|
353
|
+
int w = NUM2INT(width);
|
|
354
|
+
int h = NUM2INT(height);
|
|
355
|
+
NSString *t = [NSString stringWithUTF8String:StringValueCStr(title)];
|
|
356
|
+
|
|
357
|
+
RBGLWindow *window = [[RBGLWindow alloc] initWithWidth:w height:h title:t];
|
|
358
|
+
return ULONG2NUM((unsigned long)(__bridge_retained void *)window);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
static VALUE cocoa_window_destroy(VALUE self, VALUE window_ptr) {
|
|
363
|
+
void *ptr = (void *)NUM2ULONG(window_ptr);
|
|
364
|
+
if (!ptr) return Qnil;
|
|
365
|
+
|
|
366
|
+
// Get window reference without transferring ownership yet
|
|
367
|
+
RBGLWindow *window = (__bridge RBGLWindow *)ptr;
|
|
368
|
+
|
|
369
|
+
// Synchronize GPU before cleanup (outside autoreleasepool)
|
|
370
|
+
if (window && window.useMetal && window.metalView) {
|
|
371
|
+
RBGLMetalView *metalView = window.metalView;
|
|
372
|
+
if (metalView.commandQueue) {
|
|
373
|
+
@try {
|
|
374
|
+
id<MTLCommandBuffer> syncBuffer = [metalView.commandQueue commandBuffer];
|
|
375
|
+
if (syncBuffer) {
|
|
376
|
+
[syncBuffer commit];
|
|
377
|
+
[syncBuffer waitUntilCompleted];
|
|
378
|
+
}
|
|
379
|
+
} @catch (NSException *e) {
|
|
380
|
+
// Ignore - GPU may already be done
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
// Mark that we've cleaned up the compute shader
|
|
384
|
+
metalView.hasComputeShader = NO;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Close the window
|
|
388
|
+
if (window) {
|
|
389
|
+
[window close];
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Now transfer ownership to ARC - this will release the object
|
|
393
|
+
// Do this outside autoreleasepool to avoid double-release issues
|
|
394
|
+
(void)(__bridge_transfer id)ptr;
|
|
395
|
+
|
|
396
|
+
return Qnil;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
static VALUE cocoa_set_pixels(VALUE self, VALUE window_ptr, VALUE buffer, VALUE width, VALUE height) {
|
|
400
|
+
@autoreleasepool {
|
|
401
|
+
RBGLWindow *window = (__bridge RBGLWindow *)(void *)NUM2ULONG(window_ptr);
|
|
402
|
+
int w = NUM2INT(width);
|
|
403
|
+
int h = NUM2INT(height);
|
|
404
|
+
|
|
405
|
+
Check_Type(buffer, T_STRING);
|
|
406
|
+
const unsigned char *data = (const unsigned char *)RSTRING_PTR(buffer);
|
|
407
|
+
long len = RSTRING_LEN(buffer);
|
|
408
|
+
|
|
409
|
+
if (len < w * h * 4) {
|
|
410
|
+
rb_raise(rb_eArgError, "Buffer too small");
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (window.useMetal) {
|
|
414
|
+
[window.metalView updatePixels:data];
|
|
415
|
+
} else {
|
|
416
|
+
unsigned char *bitmapData = [window.bitmapRep bitmapData];
|
|
417
|
+
memcpy(bitmapData, data, w * h * 4);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return Qnil;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
static VALUE cocoa_present(VALUE self, VALUE window_ptr) {
|
|
424
|
+
@autoreleasepool {
|
|
425
|
+
RBGLWindow *window = (__bridge RBGLWindow *)(void *)NUM2ULONG(window_ptr);
|
|
426
|
+
|
|
427
|
+
if (window.useMetal) {
|
|
428
|
+
[window.metalView present];
|
|
429
|
+
} else {
|
|
430
|
+
[window.imageView setNeedsDisplay:YES];
|
|
431
|
+
[window displayIfNeeded];
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return Qnil;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
static VALUE cocoa_poll_events(VALUE self, VALUE window_ptr) {
|
|
438
|
+
@autoreleasepool {
|
|
439
|
+
RBGLWindow *window = (__bridge RBGLWindow *)(void *)NUM2ULONG(window_ptr);
|
|
440
|
+
|
|
441
|
+
NSEvent *event;
|
|
442
|
+
while ((event = [NSApp nextEventMatchingMask:NSEventMaskAny
|
|
443
|
+
untilDate:nil
|
|
444
|
+
inMode:NSDefaultRunLoopMode
|
|
445
|
+
dequeue:YES])) {
|
|
446
|
+
[NSApp sendEvent:event];
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
|
|
450
|
+
beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.001]];
|
|
451
|
+
[NSApp updateWindows];
|
|
452
|
+
|
|
453
|
+
VALUE events = rb_ary_new();
|
|
454
|
+
for (NSDictionary *dict in window.pendingEvents) {
|
|
455
|
+
VALUE hash = rb_hash_new();
|
|
456
|
+
|
|
457
|
+
NSString *type = dict[@"type"];
|
|
458
|
+
rb_hash_aset(hash, ID2SYM(rb_intern("type")),
|
|
459
|
+
ID2SYM(rb_intern([type UTF8String])));
|
|
460
|
+
|
|
461
|
+
if (dict[@"key"]) {
|
|
462
|
+
rb_hash_aset(hash, ID2SYM(rb_intern("key")), INT2NUM([dict[@"key"] intValue]));
|
|
463
|
+
}
|
|
464
|
+
if (dict[@"char"]) {
|
|
465
|
+
rb_hash_aset(hash, ID2SYM(rb_intern("char")),
|
|
466
|
+
rb_str_new_cstr([dict[@"char"] UTF8String]));
|
|
467
|
+
}
|
|
468
|
+
if (dict[@"x"]) {
|
|
469
|
+
rb_hash_aset(hash, ID2SYM(rb_intern("x")), DBL2NUM([dict[@"x"] doubleValue]));
|
|
470
|
+
}
|
|
471
|
+
if (dict[@"y"]) {
|
|
472
|
+
rb_hash_aset(hash, ID2SYM(rb_intern("y")), DBL2NUM([dict[@"y"] doubleValue]));
|
|
473
|
+
}
|
|
474
|
+
if (dict[@"button"]) {
|
|
475
|
+
rb_hash_aset(hash, ID2SYM(rb_intern("button")), INT2NUM([dict[@"button"] intValue]));
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
rb_ary_push(events, hash);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
[window.pendingEvents removeAllObjects];
|
|
482
|
+
return events;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
static VALUE cocoa_should_close(VALUE self, VALUE window_ptr) {
|
|
487
|
+
RBGLWindow *window = (__bridge RBGLWindow *)(void *)NUM2ULONG(window_ptr);
|
|
488
|
+
return window.shouldClose ? Qtrue : Qfalse;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// ========== Compute Shader Bridge Functions ==========
|
|
492
|
+
|
|
493
|
+
static VALUE cocoa_metal_compute_available(VALUE self, VALUE window_ptr) {
|
|
494
|
+
RBGLWindow *window = (__bridge RBGLWindow *)(void *)NUM2ULONG(window_ptr);
|
|
495
|
+
return (window.useMetal && window.metalView.device) ? Qtrue : Qfalse;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
static VALUE cocoa_compile_compute_shader(VALUE self, VALUE window_ptr, VALUE msl_source) {
|
|
499
|
+
@autoreleasepool {
|
|
500
|
+
RBGLWindow *window = (__bridge RBGLWindow *)(void *)NUM2ULONG(window_ptr);
|
|
501
|
+
if (!window.useMetal) {
|
|
502
|
+
rb_raise(rb_eRuntimeError, "Metal not available");
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
Check_Type(msl_source, T_STRING);
|
|
506
|
+
NSString *source = [NSString stringWithUTF8String:StringValueCStr(msl_source)];
|
|
507
|
+
NSError *error = nil;
|
|
508
|
+
|
|
509
|
+
if (![window.metalView compileComputeShader:source error:&error]) {
|
|
510
|
+
rb_raise(rb_eRuntimeError, "Failed to compile shader: %s",
|
|
511
|
+
[[error localizedDescription] UTF8String]);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
return Qtrue;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
static VALUE cocoa_dispatch_compute(VALUE self, VALUE window_ptr, VALUE uniform_data) {
|
|
518
|
+
@autoreleasepool {
|
|
519
|
+
RBGLWindow *window = (__bridge RBGLWindow *)(void *)NUM2ULONG(window_ptr);
|
|
520
|
+
if (!window.useMetal || !window.metalView.hasComputeShader) {
|
|
521
|
+
rb_raise(rb_eRuntimeError, "Compute shader not compiled");
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
Check_Type(uniform_data, T_STRING);
|
|
525
|
+
const void *data = RSTRING_PTR(uniform_data);
|
|
526
|
+
long length = RSTRING_LEN(uniform_data);
|
|
527
|
+
|
|
528
|
+
[window.metalView dispatchComputeWithUniforms:data length:length];
|
|
529
|
+
}
|
|
530
|
+
return Qnil;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
static VALUE cocoa_present_compute(VALUE self, VALUE window_ptr) {
|
|
534
|
+
@autoreleasepool {
|
|
535
|
+
RBGLWindow *window = (__bridge RBGLWindow *)(void *)NUM2ULONG(window_ptr);
|
|
536
|
+
if (window.useMetal && window.metalView.hasComputeShader) {
|
|
537
|
+
[window.metalView presentCompute];
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
return Qnil;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
static VALUE cocoa_has_compute_shader(VALUE self, VALUE window_ptr) {
|
|
544
|
+
RBGLWindow *window = (__bridge RBGLWindow *)(void *)NUM2ULONG(window_ptr);
|
|
545
|
+
return (window.useMetal && window.metalView.hasComputeShader) ? Qtrue : Qfalse;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
void Init_rbgl_cocoa_bridge(void) {
|
|
549
|
+
VALUE mRBGL = rb_define_module("RBGL");
|
|
550
|
+
VALUE mBridge = rb_define_module_under(mRBGL, "CocoaBridge");
|
|
551
|
+
|
|
552
|
+
rb_define_module_function(mBridge, "init", cocoa_init, 0);
|
|
553
|
+
rb_define_module_function(mBridge, "window_create", cocoa_window_create, 3);
|
|
554
|
+
rb_define_module_function(mBridge, "window_destroy", cocoa_window_destroy, 1);
|
|
555
|
+
rb_define_module_function(mBridge, "set_pixels", cocoa_set_pixels, 4);
|
|
556
|
+
rb_define_module_function(mBridge, "present", cocoa_present, 1);
|
|
557
|
+
rb_define_module_function(mBridge, "poll_events", cocoa_poll_events, 1);
|
|
558
|
+
rb_define_module_function(mBridge, "should_close?", cocoa_should_close, 1);
|
|
559
|
+
|
|
560
|
+
// Compute shader functions
|
|
561
|
+
rb_define_module_function(mBridge, "metal_compute_available?", cocoa_metal_compute_available, 1);
|
|
562
|
+
rb_define_module_function(mBridge, "compile_compute_shader", cocoa_compile_compute_shader, 2);
|
|
563
|
+
rb_define_module_function(mBridge, "dispatch_compute", cocoa_dispatch_compute, 2);
|
|
564
|
+
rb_define_module_function(mBridge, "present_compute", cocoa_present_compute, 1);
|
|
565
|
+
rb_define_module_function(mBridge, "has_compute_shader?", cocoa_has_compute_shader, 1);
|
|
566
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "rbgl_cocoa_bridge/version"
|
|
4
|
+
|
|
5
|
+
begin
|
|
6
|
+
require "rbgl_cocoa_bridge/rbgl_cocoa_bridge"
|
|
7
|
+
rescue LoadError
|
|
8
|
+
# Extension not compiled - this is expected on non-macOS platforms
|
|
9
|
+
module RBGL
|
|
10
|
+
module CocoaBridge
|
|
11
|
+
def self.init
|
|
12
|
+
raise LoadError, "Cocoa bridge is only available on macOS"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.window_create(_w, _h, _t)
|
|
16
|
+
raise LoadError, "Cocoa bridge is only available on macOS"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.window_destroy(_handle)
|
|
20
|
+
raise LoadError, "Cocoa bridge is only available on macOS"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.set_pixels(_handle, _data, _w, _h)
|
|
24
|
+
raise LoadError, "Cocoa bridge is only available on macOS"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.present(_handle)
|
|
28
|
+
raise LoadError, "Cocoa bridge is only available on macOS"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.poll_events(_handle)
|
|
32
|
+
raise LoadError, "Cocoa bridge is only available on macOS"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.should_close?(_handle)
|
|
36
|
+
raise LoadError, "Cocoa bridge is only available on macOS"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lib/rbgl_cocoa_bridge/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "rbgl_cocoa_bridge"
|
|
7
|
+
spec.version = RBGL::CocoaBridge::VERSION
|
|
8
|
+
spec.authors = ["Yudai Takada"]
|
|
9
|
+
spec.email = ["t.yudai92@gmail.com"]
|
|
10
|
+
|
|
11
|
+
spec.summary = "Native macOS Cocoa/Metal bridge for Ruby graphics applications"
|
|
12
|
+
spec.description = "A Ruby C extension providing native macOS window management and Metal GPU acceleration for graphics applications."
|
|
13
|
+
spec.homepage = "https://github.com/ydah/rbgl_cocoa_bridge"
|
|
14
|
+
spec.license = "MIT"
|
|
15
|
+
spec.required_ruby_version = ">= 3.0.0"
|
|
16
|
+
|
|
17
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
|
18
|
+
spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
|
|
19
|
+
|
|
20
|
+
spec.files = Dir.chdir(__dir__) do
|
|
21
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
|
22
|
+
(File.expand_path(f) == __FILE__) ||
|
|
23
|
+
f.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile])
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
spec.require_paths = ["lib"]
|
|
27
|
+
spec.extensions = ["ext/rbgl_cocoa_bridge/extconf.rb"]
|
|
28
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: rbgl_cocoa_bridge
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Yudai Takada
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: A Ruby C extension providing native macOS window management and Metal
|
|
13
|
+
GPU acceleration for graphics applications.
|
|
14
|
+
email:
|
|
15
|
+
- t.yudai92@gmail.com
|
|
16
|
+
executables: []
|
|
17
|
+
extensions:
|
|
18
|
+
- ext/rbgl_cocoa_bridge/extconf.rb
|
|
19
|
+
extra_rdoc_files: []
|
|
20
|
+
files:
|
|
21
|
+
- CHANGELOG.md
|
|
22
|
+
- LICENSE
|
|
23
|
+
- README.md
|
|
24
|
+
- Rakefile
|
|
25
|
+
- ext/rbgl_cocoa_bridge/extconf.rb
|
|
26
|
+
- ext/rbgl_cocoa_bridge/rbgl_cocoa_bridge.m
|
|
27
|
+
- lib/rbgl_cocoa_bridge.rb
|
|
28
|
+
- lib/rbgl_cocoa_bridge/version.rb
|
|
29
|
+
- rbgl_cocoa_bridge.gemspec
|
|
30
|
+
homepage: https://github.com/ydah/rbgl_cocoa_bridge
|
|
31
|
+
licenses:
|
|
32
|
+
- MIT
|
|
33
|
+
metadata:
|
|
34
|
+
source_code_uri: https://github.com/ydah/rbgl_cocoa_bridge
|
|
35
|
+
changelog_uri: https://github.com/ydah/rbgl_cocoa_bridge/blob/main/CHANGELOG.md
|
|
36
|
+
rdoc_options: []
|
|
37
|
+
require_paths:
|
|
38
|
+
- lib
|
|
39
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
40
|
+
requirements:
|
|
41
|
+
- - ">="
|
|
42
|
+
- !ruby/object:Gem::Version
|
|
43
|
+
version: 3.0.0
|
|
44
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
45
|
+
requirements:
|
|
46
|
+
- - ">="
|
|
47
|
+
- !ruby/object:Gem::Version
|
|
48
|
+
version: '0'
|
|
49
|
+
requirements: []
|
|
50
|
+
rubygems_version: 4.0.3
|
|
51
|
+
specification_version: 4
|
|
52
|
+
summary: Native macOS Cocoa/Metal bridge for Ruby graphics applications
|
|
53
|
+
test_files: []
|