rays 0.3.12 → 0.3.13
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 +4 -4
- data/.github/workflows/release-gem.yml +2 -16
- data/ChangeLog.md +9 -0
- data/README.md +164 -5
- data/VERSION +1 -1
- data/rays.gemspec +2 -2
- data/src/opengl/painter.cpp +196 -115
- data/src/painter.cpp +17 -18
- data/src/painter.h +13 -13
- metadata +6 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: be106158650d37c835a8396c0ce079a3bc829f6fdd10fed78153f5bba6e0246d
|
|
4
|
+
data.tar.gz: 833c9b8cd83d1e484d8424991cffd89af1a4af0a31eead4cfe8ea52db0dacead
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ff626e5099d975fa64f4990d3c4ee12265612dde05b56103354f1cbbbbba6653770c183b262b757d103bf3e1c31824813d1afe66b3445c33090448587c81441a
|
|
7
|
+
data.tar.gz: 590f20a70dc9505ab609482ce98ff7fbcc9eddbdf03271597869dfadbec55a307f775327c41ffa7f630eef5d7dc7c0c6344d78070e9c5888f2c3bdef0f1de7cf
|
|
@@ -36,23 +36,9 @@ jobs:
|
|
|
36
36
|
echo path=$(ruby -e 'print Dir.glob("*.gem").first') >> $GITHUB_OUTPUT
|
|
37
37
|
|
|
38
38
|
- name: create github release
|
|
39
|
-
id: release
|
|
40
|
-
uses: actions/create-release@v1
|
|
41
39
|
env:
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
tag_name: ${{ github.ref }}
|
|
45
|
-
release_name: ${{ github.ref }}
|
|
46
|
-
|
|
47
|
-
- name: upload to github release
|
|
48
|
-
uses: actions/upload-release-asset@v1
|
|
49
|
-
env:
|
|
50
|
-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
51
|
-
with:
|
|
52
|
-
upload_url: ${{ steps.release.outputs.upload_url }}
|
|
53
|
-
asset_path: ./${{ steps.gem.outputs.path }}
|
|
54
|
-
asset_name: ${{ steps.gem.outputs.path }}
|
|
55
|
-
asset_content_type: application/zip
|
|
40
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
41
|
+
run: ruby -I.github/workflows -rutils -e 'release(*ARGV)' ./${{ steps.gem.outputs.path }}
|
|
56
42
|
|
|
57
43
|
- name: upload to rubygems
|
|
58
44
|
env:
|
data/ChangeLog.md
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
# rays ChangeLog
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## [v0.3.13] - 2026-05-17
|
|
5
|
+
|
|
6
|
+
- Improve batch rendering: defer flush until state actually changes
|
|
7
|
+
- Rewrite README.md
|
|
8
|
+
- CI: Migrate release-gem.yml from actions/create-release to gh release create
|
|
9
|
+
|
|
10
|
+
- Fix text rendering corruption when batching is enabled
|
|
11
|
+
|
|
12
|
+
|
|
4
13
|
## [v0.3.12] - 2026-05-10
|
|
5
14
|
|
|
6
15
|
- Support WebAssembly
|
data/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Rays - A
|
|
1
|
+
# Rays - A 2D drawing engine on OpenGL
|
|
2
2
|
|
|
3
3
|
[](https://deepwiki.com/xord/rays)
|
|
4
4
|

|
|
@@ -21,18 +21,39 @@ Thanks for your support! 🙌
|
|
|
21
21
|
|
|
22
22
|
## 🚀 About
|
|
23
23
|
|
|
24
|
-
**Rays** is a drawing engine
|
|
24
|
+
**Rays** is a hardware-accelerated 2D drawing engine for Ruby. It is built on OpenGL and exposes a retained-mode-friendly API: build `Polygon`, `Polyline`, `Image`, `Shader`, and `Font` objects, then paint them into an off-screen `Image` (or a window provided by [Reflex](https://github.com/xord/reflex)) through a `Painter`.
|
|
25
25
|
|
|
26
|
-
It is
|
|
26
|
+
It is the rendering layer used by [Reflex](https://github.com/xord/reflex), [Processing](https://github.com/xord/processing), [RubySketch](https://github.com/xord/rubysketch), and [Reight](https://github.com/xord/reight). Like the rest of the `xord/*` family, it is primarily developed for our own use, but it also works as a standalone drawing gem.
|
|
27
|
+
|
|
28
|
+
## 📋 Requirements
|
|
29
|
+
|
|
30
|
+
- Ruby **3.0.0** or later
|
|
31
|
+
- A C++ compiler with C++20 support
|
|
32
|
+
- [Xot](https://rubygems.org/gems/xot) and [Rucy](https://rubygems.org/gems/rucy) (declared as runtime dependencies)
|
|
33
|
+
- Platform graphics backend:
|
|
34
|
+
- **macOS** — AppKit, OpenGL, AVFoundation (bundled with the OS)
|
|
35
|
+
- **iOS** — UIKit, OpenGL ES, AVFoundation (bundled with the OS)
|
|
36
|
+
- **Windows** — GDI32, OpenGL32, GLEW (`MINGW_PACKAGE_PREFIX-glew`)
|
|
37
|
+
- **Linux** — `libsdl2-dev`, `libsdl2-ttf-dev`, `libglew-dev`
|
|
38
|
+
|
|
39
|
+
The following third-party libraries are cloned from GitHub and statically linked while the native extension is being built, so you do not need to install them separately:
|
|
40
|
+
|
|
41
|
+
| Library | Role |
|
|
42
|
+
| ------------------------------------------------------------- | ------------------------------------------------- |
|
|
43
|
+
| [GLM](https://github.com/g-truc/glm) | Vector / matrix math used internally |
|
|
44
|
+
| [Clipper](https://github.com/skyrpex/clipper) | Polygon Boolean operations (`+`, `-`, `&`, `\|`, `^`) |
|
|
45
|
+
| [earcut.hpp](https://github.com/mapbox/earcut.hpp) | Polygon triangulation |
|
|
46
|
+
| [splines-lib](https://github.com/andrewwillmott/splines-lib) | Curve / spline math |
|
|
47
|
+
| [stb](https://github.com/nothings/stb) (Windows / Linux only) | Image file loading |
|
|
27
48
|
|
|
28
49
|
## 📦 Installation
|
|
29
50
|
|
|
30
51
|
Add this line to your Gemfile:
|
|
31
52
|
```ruby
|
|
32
|
-
|
|
53
|
+
gem 'rays'
|
|
33
54
|
```
|
|
34
55
|
|
|
35
|
-
Then
|
|
56
|
+
Then install:
|
|
36
57
|
```bash
|
|
37
58
|
$ bundle install
|
|
38
59
|
```
|
|
@@ -42,7 +63,145 @@ Or install it directly:
|
|
|
42
63
|
$ gem install rays
|
|
43
64
|
```
|
|
44
65
|
|
|
66
|
+
`require 'rays'` automatically calls `Rays.init!` and registers `Rays.fin!` at exit. Set `$RAYS_NOAUTOINIT = true` before requiring if you want to manage the lifetime yourself.
|
|
67
|
+
|
|
68
|
+
Rays needs a current OpenGL context. When used through [Reflex](https://github.com/xord/reflex), the window creates and binds a context for you. To use Rays standalone for off-screen rendering, it allocates a hidden context automatically.
|
|
69
|
+
|
|
70
|
+
## 📚 What's Included
|
|
71
|
+
|
|
72
|
+
### Geometry and color types
|
|
73
|
+
|
|
74
|
+
| Class | Purpose |
|
|
75
|
+
| -------------------- | ---------------------------------------------------------------- |
|
|
76
|
+
| `Rays::Point` | 2D / 3D point with arithmetic operators |
|
|
77
|
+
| `Rays::Bounds` | Axis-aligned rectangle (position + size) |
|
|
78
|
+
| `Rays::Color` | RGBA color in floating-point components |
|
|
79
|
+
| `Rays::ColorSpace` | Pixel format / color space descriptor (RGBA, ARGB, GRAY, ...) |
|
|
80
|
+
| `Rays::Matrix` | 4×4 transformation matrix |
|
|
81
|
+
|
|
82
|
+
### Drawing primitives
|
|
83
|
+
|
|
84
|
+
| Class | Purpose |
|
|
85
|
+
| -------------------- | -------------------------------------------------------------------------------------------- |
|
|
86
|
+
| `Rays::Polyline` | A single open or closed polyline; expandable into a stroked polygon |
|
|
87
|
+
| `Rays::Polygon` | One or more polylines forming a closed shape; supports Boolean ops via `+`, `-`, `&`, `\|`, `^` |
|
|
88
|
+
| `Rays::Image` | A renderable texture with an associated `Painter` for off-screen drawing |
|
|
89
|
+
| `Rays::Bitmap` | CPU-side pixel buffer that can be uploaded to / downloaded from an `Image` |
|
|
90
|
+
| `Rays::Font` | Text rendering — created from a system font name and a size |
|
|
91
|
+
| `Rays::Shader` | GLSL fragment / vertex shader with `set_uniform` / `uniform` for parameters |
|
|
92
|
+
| `Rays::Camera` | Live camera capture (per platform) rendered into an `Image` |
|
|
93
|
+
|
|
94
|
+
### The `Painter`
|
|
95
|
+
|
|
96
|
+
`Rays::Painter` is the immediate-mode drawing surface. Obtain one through `Image#painter`, then between `painter.paint do |p| ... end` (or `begin_paint` / `end_paint`) issue draw calls:
|
|
97
|
+
|
|
98
|
+
- **Shapes** — `point`, `line`, `rect`, `ellipse`, `curve`, `bezier`, `triangle`, `quad`, `polygon`
|
|
99
|
+
- **Bitmaps & text** — `image`, `text`
|
|
100
|
+
- **State** — `fill`, `stroke`, `background`, `stroke_width`, `stroke_cap`, `stroke_join`, `blend_mode`, `clip`, `font`, `texture`, `shader`
|
|
101
|
+
- **Transforms** — `translate`, `scale`, `rotate`, `set_matrix`
|
|
102
|
+
- **Push / pop** — `push(:state, :matrix)` / `pop`, or the block form `push(fill: ..., stroke: ...) { ... }`
|
|
103
|
+
|
|
104
|
+
`stroke_cap`, `stroke_join`, `blend_mode`, `texcoord_mode`, `texcoord_wrap` accept symbols (e.g. `:round`, `:miter`, `:multiply`).
|
|
105
|
+
|
|
106
|
+
### Top-level helpers
|
|
107
|
+
|
|
108
|
+
- `Rays.init!` / `Rays.fin!` — explicit lifecycle (called automatically on `require`)
|
|
109
|
+
- `Rays::Image.load(path)` / `Rays::Image#save(path)` — image file I/O
|
|
110
|
+
- `Rays.renderer_info` — debug info about the current OpenGL context
|
|
111
|
+
|
|
112
|
+
## 💡 Usage
|
|
113
|
+
|
|
114
|
+
### Draw to an off-screen image and save it
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
require 'rays'
|
|
118
|
+
|
|
119
|
+
image = Rays::Image.new(200, 200)
|
|
120
|
+
image.paint do |p|
|
|
121
|
+
p.background 0, 0, 0
|
|
122
|
+
p.fill 1, 0.4, 0.1 # orange-ish
|
|
123
|
+
p.stroke 1, 1, 1
|
|
124
|
+
p.stroke_width 4
|
|
125
|
+
|
|
126
|
+
p.rect 20, 20, 160, 160, round: 16
|
|
127
|
+
p.ellipse 100, 100, 60, 60
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
image.save 'out.png'
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Compose a polygon from Boolean operations
|
|
134
|
+
|
|
135
|
+
```ruby
|
|
136
|
+
require 'rays'
|
|
137
|
+
|
|
138
|
+
a = Rays::Polygon.rect(0, 0, 100, 100)
|
|
139
|
+
b = Rays::Polygon.ellipse(50, 50, 80, 80)
|
|
140
|
+
|
|
141
|
+
union = a | b # outer outline of both
|
|
142
|
+
difference = a - b # rectangle with circular bite
|
|
143
|
+
intersection = a & b # lens-shaped overlap
|
|
144
|
+
xor = a ^ b # everything except the overlap
|
|
145
|
+
|
|
146
|
+
Rays::Image.new(140, 140).paint {|p| p.fill 1; p.polygon difference }.save 'diff.png'
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
(The C++ operator names map to Ruby's `|`, `-`, `&`, `^`.)
|
|
150
|
+
|
|
151
|
+
### Use a fragment shader
|
|
152
|
+
|
|
153
|
+
```ruby
|
|
154
|
+
require 'rays'
|
|
155
|
+
|
|
156
|
+
invert = Rays::Shader.new <<~GLSL
|
|
157
|
+
uniform sampler2D texture;
|
|
158
|
+
varying vec4 vTexCoord;
|
|
159
|
+
void main() {
|
|
160
|
+
vec4 c = texture2D(texture, vTexCoord.xy);
|
|
161
|
+
gl_FragColor = vec4(1.0 - c.rgb, c.a);
|
|
162
|
+
}
|
|
163
|
+
GLSL
|
|
164
|
+
|
|
165
|
+
src = Rays::Image.load('photo.png')
|
|
166
|
+
out = Rays::Image.new(src.width, src.height)
|
|
167
|
+
out.paint do |p|
|
|
168
|
+
p.shader = invert
|
|
169
|
+
p.image src
|
|
170
|
+
end
|
|
171
|
+
out.save 'photo-invert.png'
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Drive `push` / `pop` with attributes
|
|
175
|
+
|
|
176
|
+
```ruby
|
|
177
|
+
image.paint do |p|
|
|
178
|
+
p.push(:state, :matrix, fill: [1, 0, 0]) do
|
|
179
|
+
p.translate 50, 50
|
|
180
|
+
p.rotate 30
|
|
181
|
+
p.rect -20, -20, 40, 40
|
|
182
|
+
end
|
|
183
|
+
# fill, state, and matrix are restored here
|
|
184
|
+
end
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## 🛠️ Development
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
$ rake vendor # clone the external libraries into vendor/
|
|
191
|
+
$ rake lib # build the native C++ library (librays)
|
|
192
|
+
$ rake ext # build the Ruby C extension
|
|
193
|
+
$ rake test # run the test suite
|
|
194
|
+
$ rake doc # generate RDoc from C++ sources
|
|
195
|
+
$ rake # default: builds the extension
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
The drawing tests render into off-screen images and compare pixels, so they require a working OpenGL context. The `test_rays_init.rb` test must run in its own process and is listed in `TESTS_ALONE`.
|
|
199
|
+
|
|
200
|
+
In the [`xord/all`](https://github.com/xord/all) monorepo you can scope by module, e.g. `rake rays test`.
|
|
201
|
+
|
|
45
202
|
## 📜 License
|
|
46
203
|
|
|
47
204
|
**Rays** is licensed under the MIT License.
|
|
48
205
|
See the [LICENSE](./LICENSE) file for details.
|
|
206
|
+
|
|
207
|
+
The third-party libraries listed above retain their own licenses (all MIT-compatible: MIT, ISC, Boost-1.0, dual-licensed MIT/Happy-Bunny for GLM, Unlicense for splines-lib).
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.3.
|
|
1
|
+
0.3.13
|
data/rays.gemspec
CHANGED
|
@@ -25,8 +25,8 @@ Gem::Specification.new do |s|
|
|
|
25
25
|
s.platform = Gem::Platform::RUBY
|
|
26
26
|
s.required_ruby_version = '>= 3.0.0'
|
|
27
27
|
|
|
28
|
-
s.add_dependency 'xot', '~> 0.3.
|
|
29
|
-
s.add_dependency 'rucy', '~> 0.3.
|
|
28
|
+
s.add_dependency 'xot', '~> 0.3.13'
|
|
29
|
+
s.add_dependency 'rucy', '~> 0.3.13'
|
|
30
30
|
|
|
31
31
|
s.files = `git ls-files`.split $/
|
|
32
32
|
s.executables = s.files.grep(%r{^bin/}) {|f| File.basename f}
|
data/src/opengl/painter.cpp
CHANGED
|
@@ -24,6 +24,11 @@ namespace Rays
|
|
|
24
24
|
{
|
|
25
25
|
|
|
26
26
|
|
|
27
|
+
static const Shader INVALID_SHADER;
|
|
28
|
+
|
|
29
|
+
static const Texture INVALID_TEXTURE;
|
|
30
|
+
|
|
31
|
+
|
|
27
32
|
struct Vector4 : Coord4
|
|
28
33
|
{
|
|
29
34
|
|
|
@@ -140,11 +145,23 @@ namespace Rays
|
|
|
140
145
|
struct Batcher
|
|
141
146
|
{
|
|
142
147
|
|
|
148
|
+
GLuint cached_shader_id = 0;
|
|
149
|
+
|
|
150
|
+
GLuint cached_texture_id = 0;
|
|
151
|
+
|
|
143
152
|
int count = 0;
|
|
144
153
|
|
|
145
|
-
|
|
154
|
+
BlendMode blend_mode;
|
|
155
|
+
|
|
156
|
+
TexCoordMode texcoord_mode;
|
|
146
157
|
|
|
147
|
-
|
|
158
|
+
TexCoordWrap texcoord_wrap;
|
|
159
|
+
|
|
160
|
+
Bounds clip;
|
|
161
|
+
|
|
162
|
+
Shader shader = INVALID_SHADER;
|
|
163
|
+
|
|
164
|
+
Texture texture = INVALID_TEXTURE;
|
|
148
165
|
|
|
149
166
|
std::vector<Vector4> points;
|
|
150
167
|
|
|
@@ -158,11 +175,25 @@ namespace Rays
|
|
|
158
175
|
|
|
159
176
|
std::vector<Coord3> texcoord_maxes;
|
|
160
177
|
|
|
161
|
-
void
|
|
178
|
+
void init (const PainterState& state)
|
|
162
179
|
{
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
180
|
+
cached_shader_id = 0;
|
|
181
|
+
cached_texture_id = 0;
|
|
182
|
+
blend_mode = state.blend_mode;
|
|
183
|
+
texcoord_mode = state.texcoord_mode;
|
|
184
|
+
texcoord_wrap = state.texcoord_wrap;
|
|
185
|
+
clip = state.clip;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
void cleanup ()
|
|
189
|
+
{
|
|
190
|
+
shader = INVALID_SHADER;
|
|
191
|
+
texture = INVALID_TEXTURE;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
void clear_buffers ()
|
|
195
|
+
{
|
|
196
|
+
count = 0;
|
|
166
197
|
points .clear();
|
|
167
198
|
indices .clear();
|
|
168
199
|
colors .clear();
|
|
@@ -226,6 +257,83 @@ namespace Rays
|
|
|
226
257
|
buffers.clear();
|
|
227
258
|
}
|
|
228
259
|
|
|
260
|
+
void apply_blend_mode ()
|
|
261
|
+
{
|
|
262
|
+
switch (state.blend_mode)
|
|
263
|
+
{
|
|
264
|
+
case BLEND_NORMAL:
|
|
265
|
+
glBlendEquationSeparate(GL_FUNC_ADD, GL_FUNC_ADD);
|
|
266
|
+
glBlendFuncSeparate(
|
|
267
|
+
GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE);
|
|
268
|
+
break;
|
|
269
|
+
|
|
270
|
+
case BLEND_ADD:
|
|
271
|
+
glBlendEquationSeparate(GL_FUNC_ADD, GL_FUNC_ADD);
|
|
272
|
+
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE, GL_ONE, GL_ONE);
|
|
273
|
+
break;
|
|
274
|
+
|
|
275
|
+
case BLEND_SUBTRACT:
|
|
276
|
+
glBlendEquationSeparate(GL_FUNC_REVERSE_SUBTRACT, GL_FUNC_ADD);
|
|
277
|
+
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE, GL_ONE, GL_ONE);
|
|
278
|
+
break;
|
|
279
|
+
|
|
280
|
+
case BLEND_LIGHTEST:
|
|
281
|
+
glBlendEquationSeparate(GL_MAX, GL_FUNC_ADD);
|
|
282
|
+
glBlendFuncSeparate(GL_ONE, GL_ONE, GL_ONE, GL_ONE);
|
|
283
|
+
break;
|
|
284
|
+
|
|
285
|
+
case BLEND_DARKEST:
|
|
286
|
+
glBlendEquationSeparate(GL_MIN, GL_FUNC_ADD);
|
|
287
|
+
glBlendFuncSeparate(GL_ONE, GL_ONE, GL_ONE, GL_ONE);
|
|
288
|
+
break;
|
|
289
|
+
|
|
290
|
+
case BLEND_EXCLUSION:
|
|
291
|
+
glBlendEquationSeparate(GL_FUNC_ADD, GL_FUNC_ADD);
|
|
292
|
+
glBlendFuncSeparate(
|
|
293
|
+
GL_ONE_MINUS_DST_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_ONE, GL_ONE);
|
|
294
|
+
break;
|
|
295
|
+
|
|
296
|
+
case BLEND_MULTIPLY:
|
|
297
|
+
glBlendEquationSeparate(GL_FUNC_ADD, GL_FUNC_ADD);
|
|
298
|
+
glBlendFuncSeparate(GL_ZERO, GL_SRC_COLOR, GL_ONE, GL_ONE);
|
|
299
|
+
break;
|
|
300
|
+
|
|
301
|
+
case BLEND_SCREEN:
|
|
302
|
+
glBlendEquationSeparate(GL_FUNC_ADD, GL_FUNC_ADD);
|
|
303
|
+
glBlendFuncSeparate(GL_ONE_MINUS_DST_COLOR, GL_ONE, GL_ONE, GL_ONE);
|
|
304
|
+
break;
|
|
305
|
+
|
|
306
|
+
case BLEND_REPLACE:
|
|
307
|
+
glBlendEquationSeparate(GL_FUNC_ADD, GL_FUNC_ADD);
|
|
308
|
+
glBlendFuncSeparate(GL_ONE, GL_ZERO, GL_ONE, GL_ZERO);
|
|
309
|
+
break;
|
|
310
|
+
|
|
311
|
+
default:
|
|
312
|
+
argument_error(__FILE__, __LINE__, "unknown blend mode");
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
OpenGL_check_error(__FILE__, __LINE__);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
void apply_clipping ()
|
|
319
|
+
{
|
|
320
|
+
const Bounds& clip = state.clip;
|
|
321
|
+
if (clip)
|
|
322
|
+
{
|
|
323
|
+
coord y = frame_buffer ? clip.y : viewport.h - (clip.y + clip.h);
|
|
324
|
+
glEnable(GL_SCISSOR_TEST);
|
|
325
|
+
glScissor(
|
|
326
|
+
pixel_density * clip.x,
|
|
327
|
+
pixel_density * y,
|
|
328
|
+
pixel_density * clip.width,
|
|
329
|
+
pixel_density * clip.height);
|
|
330
|
+
}
|
|
331
|
+
else
|
|
332
|
+
glDisable(GL_SCISSOR_TEST);
|
|
333
|
+
|
|
334
|
+
OpenGL_check_error(__FILE__, __LINE__);
|
|
335
|
+
}
|
|
336
|
+
|
|
229
337
|
};// PainterData
|
|
230
338
|
|
|
231
339
|
|
|
@@ -235,27 +343,6 @@ namespace Rays
|
|
|
235
343
|
return (PainterData*) painter->self.get();
|
|
236
344
|
}
|
|
237
345
|
|
|
238
|
-
void
|
|
239
|
-
Painter_update_clip (Painter* painter)
|
|
240
|
-
{
|
|
241
|
-
PainterData* self = get_data(painter);
|
|
242
|
-
const Bounds& clip = self->state.clip;
|
|
243
|
-
if (clip)
|
|
244
|
-
{
|
|
245
|
-
coord y = self->frame_buffer ? clip.y : self->viewport.h - (clip.y + clip.h);
|
|
246
|
-
glEnable(GL_SCISSOR_TEST);
|
|
247
|
-
glScissor(
|
|
248
|
-
self->pixel_density * clip.x,
|
|
249
|
-
self->pixel_density * y,
|
|
250
|
-
self->pixel_density * clip.width,
|
|
251
|
-
self->pixel_density * clip.height);
|
|
252
|
-
}
|
|
253
|
-
else
|
|
254
|
-
glDisable(GL_SCISSOR_TEST);
|
|
255
|
-
|
|
256
|
-
OpenGL_check_error(__FILE__, __LINE__);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
346
|
static void
|
|
260
347
|
apply_uniform (
|
|
261
348
|
const ShaderProgram& program, const char* name,
|
|
@@ -477,7 +564,7 @@ namespace Rays
|
|
|
477
564
|
static void
|
|
478
565
|
setup_texcoord_variables (
|
|
479
566
|
Matrix* matrix, Point* min, Point* max,
|
|
480
|
-
const
|
|
567
|
+
const PainterState& state, const TextureInfo& texinfo)
|
|
481
568
|
{
|
|
482
569
|
if (!texinfo.texture) return;
|
|
483
570
|
|
|
@@ -537,7 +624,7 @@ namespace Rays
|
|
|
537
624
|
|
|
538
625
|
const ShaderProgram* program = Shader_get_program(batcher.shader);
|
|
539
626
|
if (!program || !*program)
|
|
540
|
-
return batcher.
|
|
627
|
+
return batcher.clear_buffers();
|
|
541
628
|
|
|
542
629
|
ShaderProgram_activate(*program);
|
|
543
630
|
|
|
@@ -556,7 +643,63 @@ namespace Rays
|
|
|
556
643
|
self->cleanup();
|
|
557
644
|
|
|
558
645
|
ShaderProgram_deactivate();
|
|
559
|
-
batcher.
|
|
646
|
+
batcher.clear_buffers();
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
static inline GLuint
|
|
650
|
+
get_shader_program_id (const Shader& shader)
|
|
651
|
+
{
|
|
652
|
+
const ShaderProgram* p = Shader_get_program(shader);
|
|
653
|
+
return p ? p->id() : 0;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
static void
|
|
657
|
+
ensure_state_and_flush_batch (
|
|
658
|
+
Painter* painter, const Shader& shader, const Texture& texture)
|
|
659
|
+
{
|
|
660
|
+
PainterData* self = get_data(painter);
|
|
661
|
+
Batcher& b = self->batcher;
|
|
662
|
+
const PainterState& s = self->state;
|
|
663
|
+
GLuint shader_id = get_shader_program_id(shader);
|
|
664
|
+
GLuint texture_id = Texture_get_id(texture);
|
|
665
|
+
|
|
666
|
+
bool state_changed = Xot::check_and_remove_flag(
|
|
667
|
+
&self->flags, Painter::Data::UNBATCHABLE_STATE_CHANGED);
|
|
668
|
+
if (
|
|
669
|
+
!state_changed &&
|
|
670
|
+
b.cached_shader_id == shader_id &&
|
|
671
|
+
b.cached_texture_id == texture_id)
|
|
672
|
+
{
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
bool blend_changed = b.blend_mode != s.blend_mode;
|
|
677
|
+
bool clip_changed = b.clip != s.clip;
|
|
678
|
+
if (
|
|
679
|
+
b.count > 0 &&
|
|
680
|
+
(
|
|
681
|
+
blend_changed ||
|
|
682
|
+
clip_changed ||
|
|
683
|
+
b.cached_shader_id != shader_id ||
|
|
684
|
+
b.cached_texture_id != texture_id ||
|
|
685
|
+
b.texcoord_mode != s.texcoord_mode ||
|
|
686
|
+
b.texcoord_wrap != s.texcoord_wrap
|
|
687
|
+
))
|
|
688
|
+
{
|
|
689
|
+
Painter_flush(painter);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
if (blend_changed) self->apply_blend_mode();
|
|
693
|
+
if (clip_changed) self->apply_clipping();
|
|
694
|
+
|
|
695
|
+
b.cached_shader_id = shader_id;
|
|
696
|
+
b.cached_texture_id = texture_id;
|
|
697
|
+
b.blend_mode = s.blend_mode;
|
|
698
|
+
b.texcoord_mode = s.texcoord_mode;
|
|
699
|
+
b.texcoord_wrap = s.texcoord_wrap;
|
|
700
|
+
b.clip = s.clip;
|
|
701
|
+
b.shader = shader;
|
|
702
|
+
b.texture = texture;
|
|
560
703
|
}
|
|
561
704
|
|
|
562
705
|
static inline Vector4
|
|
@@ -577,16 +720,8 @@ namespace Rays
|
|
|
577
720
|
PainterData* self = get_data(painter);
|
|
578
721
|
Batcher& batcher = self->batcher;
|
|
579
722
|
|
|
580
|
-
Texture texture = texinfo ? texinfo->texture :
|
|
581
|
-
|
|
582
|
-
batcher.points.empty() ||
|
|
583
|
-
batcher.shader != shader ||
|
|
584
|
-
batcher.texture != texture)
|
|
585
|
-
{
|
|
586
|
-
Painter_flush(painter);
|
|
587
|
-
batcher.shader = shader;
|
|
588
|
-
batcher.texture = texture;
|
|
589
|
-
}
|
|
723
|
+
Texture texture = texinfo ? texinfo->texture : INVALID_TEXTURE;
|
|
724
|
+
ensure_state_and_flush_batch(painter, shader, texture);
|
|
590
725
|
|
|
591
726
|
if (++batcher.count <= 5)
|
|
592
727
|
{
|
|
@@ -732,7 +867,8 @@ namespace Rays
|
|
|
732
867
|
}
|
|
733
868
|
else
|
|
734
869
|
{
|
|
735
|
-
|
|
870
|
+
ensure_state_and_flush_batch(
|
|
871
|
+
painter, *shader, texinfo ? texinfo->texture : INVALID_TEXTURE);
|
|
736
872
|
draw(
|
|
737
873
|
self, mode, color, points, npoints, indices, nindices,
|
|
738
874
|
colors, texcoords, texinfo, *shader, self->position_matrix);
|
|
@@ -781,6 +917,10 @@ namespace Rays
|
|
|
781
917
|
{
|
|
782
918
|
assert(painter && font && line && *line != '\0');
|
|
783
919
|
|
|
920
|
+
// exclude text rendering from batching for now;
|
|
921
|
+
// text_image is shared and gets overwritten by next text draw
|
|
922
|
+
Painter_flush(painter);
|
|
923
|
+
|
|
784
924
|
Painter::Data* self = painter->self.get();
|
|
785
925
|
|
|
786
926
|
float density = self->pixel_density;
|
|
@@ -866,15 +1006,6 @@ namespace Rays
|
|
|
866
1006
|
|
|
867
1007
|
self->opengl_state.push();
|
|
868
1008
|
|
|
869
|
-
//glEnable(GL_CULL_FACE);
|
|
870
|
-
|
|
871
|
-
glEnable(GL_DEPTH_TEST);
|
|
872
|
-
glDepthFunc(GL_LEQUAL);
|
|
873
|
-
OpenGL_check_error(__FILE__, __LINE__);
|
|
874
|
-
|
|
875
|
-
glEnable(GL_BLEND);
|
|
876
|
-
set_blend_mode(self->state.blend_mode);
|
|
877
|
-
|
|
878
1009
|
FrameBuffer& fb = self->frame_buffer;
|
|
879
1010
|
if (fb)
|
|
880
1011
|
{
|
|
@@ -906,9 +1037,20 @@ namespace Rays
|
|
|
906
1037
|
|
|
907
1038
|
//self->position_matrix.translate(0.375f, 0.375f);
|
|
908
1039
|
|
|
909
|
-
|
|
1040
|
+
//glEnable(GL_CULL_FACE);
|
|
1041
|
+
|
|
1042
|
+
glEnable(GL_DEPTH_TEST);
|
|
1043
|
+
glDepthFunc(GL_LEQUAL);
|
|
1044
|
+
OpenGL_check_error(__FILE__, __LINE__);
|
|
1045
|
+
|
|
1046
|
+
glEnable(GL_BLEND);
|
|
1047
|
+
self->apply_blend_mode();
|
|
1048
|
+
self->apply_clipping();
|
|
910
1049
|
|
|
911
|
-
|
|
1050
|
+
self->batcher.init(self->state);
|
|
1051
|
+
|
|
1052
|
+
Xot::remove_flag(&self->flags, Painter::Data::UNBATCHABLE_STATE_CHANGED);
|
|
1053
|
+
Xot:: add_flag(&self->flags, Painter::Data::PAINTING);
|
|
912
1054
|
|
|
913
1055
|
glClear(GL_DEPTH_BUFFER_BIT);
|
|
914
1056
|
}
|
|
@@ -937,6 +1079,8 @@ namespace Rays
|
|
|
937
1079
|
|
|
938
1080
|
if (self->frame_buffer)
|
|
939
1081
|
FrameBuffer_unbind();
|
|
1082
|
+
|
|
1083
|
+
self->batcher.cleanup();
|
|
940
1084
|
}
|
|
941
1085
|
|
|
942
1086
|
void
|
|
@@ -953,68 +1097,5 @@ namespace Rays
|
|
|
953
1097
|
OpenGL_check_error(__FILE__, __LINE__);
|
|
954
1098
|
}
|
|
955
1099
|
|
|
956
|
-
void
|
|
957
|
-
Painter::set_blend_mode (BlendMode mode)
|
|
958
|
-
{
|
|
959
|
-
if (self->state.blend_mode != mode)
|
|
960
|
-
Painter_flush(this);
|
|
961
|
-
|
|
962
|
-
self->state.blend_mode = mode;
|
|
963
|
-
switch (mode)
|
|
964
|
-
{
|
|
965
|
-
case BLEND_NORMAL:
|
|
966
|
-
glBlendEquationSeparate(GL_FUNC_ADD, GL_FUNC_ADD);
|
|
967
|
-
glBlendFuncSeparate(
|
|
968
|
-
GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE);
|
|
969
|
-
break;
|
|
970
|
-
|
|
971
|
-
case BLEND_ADD:
|
|
972
|
-
glBlendEquationSeparate(GL_FUNC_ADD, GL_FUNC_ADD);
|
|
973
|
-
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE, GL_ONE, GL_ONE);
|
|
974
|
-
break;
|
|
975
|
-
|
|
976
|
-
case BLEND_SUBTRACT:
|
|
977
|
-
glBlendEquationSeparate(GL_FUNC_REVERSE_SUBTRACT, GL_FUNC_ADD);
|
|
978
|
-
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE, GL_ONE, GL_ONE);
|
|
979
|
-
break;
|
|
980
|
-
|
|
981
|
-
case BLEND_LIGHTEST:
|
|
982
|
-
glBlendEquationSeparate(GL_MAX, GL_FUNC_ADD);
|
|
983
|
-
glBlendFuncSeparate(GL_ONE, GL_ONE, GL_ONE, GL_ONE);
|
|
984
|
-
break;
|
|
985
|
-
|
|
986
|
-
case BLEND_DARKEST:
|
|
987
|
-
glBlendEquationSeparate(GL_MIN, GL_FUNC_ADD);
|
|
988
|
-
glBlendFuncSeparate(GL_ONE, GL_ONE, GL_ONE, GL_ONE);
|
|
989
|
-
break;
|
|
990
|
-
|
|
991
|
-
case BLEND_EXCLUSION:
|
|
992
|
-
glBlendEquationSeparate(GL_FUNC_ADD, GL_FUNC_ADD);
|
|
993
|
-
glBlendFuncSeparate(
|
|
994
|
-
GL_ONE_MINUS_DST_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_ONE, GL_ONE);
|
|
995
|
-
break;
|
|
996
|
-
|
|
997
|
-
case BLEND_MULTIPLY:
|
|
998
|
-
glBlendEquationSeparate(GL_FUNC_ADD, GL_FUNC_ADD);
|
|
999
|
-
glBlendFuncSeparate(GL_ZERO, GL_SRC_COLOR, GL_ONE, GL_ONE);
|
|
1000
|
-
break;
|
|
1001
|
-
|
|
1002
|
-
case BLEND_SCREEN:
|
|
1003
|
-
glBlendEquationSeparate(GL_FUNC_ADD, GL_FUNC_ADD);
|
|
1004
|
-
glBlendFuncSeparate(GL_ONE_MINUS_DST_COLOR, GL_ONE, GL_ONE, GL_ONE);
|
|
1005
|
-
break;
|
|
1006
|
-
|
|
1007
|
-
case BLEND_REPLACE:
|
|
1008
|
-
glBlendEquationSeparate(GL_FUNC_ADD, GL_FUNC_ADD);
|
|
1009
|
-
glBlendFuncSeparate(GL_ONE, GL_ZERO, GL_ONE, GL_ZERO);
|
|
1010
|
-
break;
|
|
1011
|
-
|
|
1012
|
-
default:
|
|
1013
|
-
argument_error(__FILE__, __LINE__, "unknown blend mode");
|
|
1014
|
-
break;
|
|
1015
|
-
}
|
|
1016
|
-
OpenGL_check_error(__FILE__, __LINE__);
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
1100
|
|
|
1020
1101
|
}// Rays
|
data/src/painter.cpp
CHANGED
|
@@ -698,6 +698,15 @@ namespace Rays
|
|
|
698
698
|
return height;
|
|
699
699
|
}
|
|
700
700
|
|
|
701
|
+
void
|
|
702
|
+
Painter::set_blend_mode (BlendMode mode)
|
|
703
|
+
{
|
|
704
|
+
if (self->state.blend_mode == mode) return;
|
|
705
|
+
|
|
706
|
+
self->state.blend_mode = mode;
|
|
707
|
+
Xot::add_flag(&self->flags, Painter::Data::UNBATCHABLE_STATE_CHANGED);
|
|
708
|
+
}
|
|
709
|
+
|
|
701
710
|
BlendMode
|
|
702
711
|
Painter::blend_mode () const
|
|
703
712
|
{
|
|
@@ -716,10 +725,8 @@ namespace Rays
|
|
|
716
725
|
if (bounds == self->state.clip)
|
|
717
726
|
return;
|
|
718
727
|
|
|
719
|
-
Painter_flush(this);
|
|
720
|
-
|
|
721
728
|
self->state.clip = bounds;
|
|
722
|
-
|
|
729
|
+
Xot::add_flag(&self->flags, Painter::Data::UNBATCHABLE_STATE_CHANGED);
|
|
723
730
|
}
|
|
724
731
|
|
|
725
732
|
void
|
|
@@ -769,9 +776,8 @@ namespace Rays
|
|
|
769
776
|
if (image == self->state.texture)
|
|
770
777
|
return;
|
|
771
778
|
|
|
772
|
-
Painter_flush(this);
|
|
773
|
-
|
|
774
779
|
self->state.texture = image;
|
|
780
|
+
Xot::add_flag(&self->flags, Painter::Data::UNBATCHABLE_STATE_CHANGED);
|
|
775
781
|
}
|
|
776
782
|
|
|
777
783
|
void
|
|
@@ -779,9 +785,8 @@ namespace Rays
|
|
|
779
785
|
{
|
|
780
786
|
if (!self->state.texture) return;
|
|
781
787
|
|
|
782
|
-
Painter_flush(this);
|
|
783
|
-
|
|
784
788
|
self->state.texture = Image();
|
|
789
|
+
Xot::add_flag(&self->flags, Painter::Data::UNBATCHABLE_STATE_CHANGED);
|
|
785
790
|
}
|
|
786
791
|
|
|
787
792
|
const Image&
|
|
@@ -796,9 +801,8 @@ namespace Rays
|
|
|
796
801
|
if (mode == self->state.texcoord_mode)
|
|
797
802
|
return;
|
|
798
803
|
|
|
799
|
-
Painter_flush(this);
|
|
800
|
-
|
|
801
804
|
self->state.texcoord_mode = mode;
|
|
805
|
+
Xot::add_flag(&self->flags, Painter::Data::UNBATCHABLE_STATE_CHANGED);
|
|
802
806
|
}
|
|
803
807
|
|
|
804
808
|
TexCoordMode
|
|
@@ -813,9 +817,8 @@ namespace Rays
|
|
|
813
817
|
if (wrap == self->state.texcoord_wrap)
|
|
814
818
|
return;
|
|
815
819
|
|
|
816
|
-
Painter_flush(this);
|
|
817
|
-
|
|
818
820
|
self->state.texcoord_wrap = wrap;
|
|
821
|
+
Xot::add_flag(&self->flags, Painter::Data::UNBATCHABLE_STATE_CHANGED);
|
|
819
822
|
}
|
|
820
823
|
|
|
821
824
|
TexCoordWrap
|
|
@@ -830,9 +833,8 @@ namespace Rays
|
|
|
830
833
|
if (shader == self->state.shader)
|
|
831
834
|
return;
|
|
832
835
|
|
|
833
|
-
Painter_flush(this);
|
|
834
|
-
|
|
835
836
|
self->state.shader = shader;
|
|
837
|
+
Xot::add_flag(&self->flags, Painter::Data::UNBATCHABLE_STATE_CHANGED);
|
|
836
838
|
}
|
|
837
839
|
|
|
838
840
|
void
|
|
@@ -840,9 +842,8 @@ namespace Rays
|
|
|
840
842
|
{
|
|
841
843
|
if (!self->state.shader) return;
|
|
842
844
|
|
|
843
|
-
Painter_flush(this);
|
|
844
|
-
|
|
845
845
|
self->state.shader = Shader();
|
|
846
|
+
Xot::add_flag(&self->flags, Painter::Data::UNBATCHABLE_STATE_CHANGED);
|
|
846
847
|
}
|
|
847
848
|
|
|
848
849
|
const Shader&
|
|
@@ -863,11 +864,9 @@ namespace Rays
|
|
|
863
864
|
if (self->state_stack.empty())
|
|
864
865
|
invalid_state_error(__FILE__, __LINE__, "state stack underflow.");
|
|
865
866
|
|
|
866
|
-
Painter_flush(this);
|
|
867
|
-
|
|
868
867
|
self->state = self->state_stack.back();
|
|
869
868
|
self->state_stack.pop_back();
|
|
870
|
-
|
|
869
|
+
Xot::add_flag(&self->flags, Painter::Data::UNBATCHABLE_STATE_CHANGED);
|
|
871
870
|
}
|
|
872
871
|
|
|
873
872
|
void
|
data/src/painter.h
CHANGED
|
@@ -59,7 +59,7 @@ namespace Rays
|
|
|
59
59
|
};// PrimitiveMode
|
|
60
60
|
|
|
61
61
|
|
|
62
|
-
struct
|
|
62
|
+
struct PainterState
|
|
63
63
|
{
|
|
64
64
|
|
|
65
65
|
Color background, colors[COLOR_TYPE_MAX];
|
|
@@ -82,16 +82,16 @@ namespace Rays
|
|
|
82
82
|
|
|
83
83
|
BlendMode blend_mode;
|
|
84
84
|
|
|
85
|
+
TexCoordMode texcoord_mode;
|
|
86
|
+
|
|
87
|
+
TexCoordWrap texcoord_wrap;
|
|
88
|
+
|
|
85
89
|
Bounds clip;
|
|
86
90
|
|
|
87
91
|
Font font;
|
|
88
92
|
|
|
89
93
|
Image texture;
|
|
90
94
|
|
|
91
|
-
TexCoordMode texcoord_mode;
|
|
92
|
-
|
|
93
|
-
TexCoordWrap texcoord_wrap;
|
|
94
|
-
|
|
95
95
|
Shader shader;
|
|
96
96
|
|
|
97
97
|
void init ()
|
|
@@ -109,11 +109,11 @@ namespace Rays
|
|
|
109
109
|
nsegment = 0;
|
|
110
110
|
line_height = -1;
|
|
111
111
|
blend_mode = BLEND_NORMAL;
|
|
112
|
+
texcoord_mode = TEXCOORD_IMAGE;
|
|
113
|
+
texcoord_wrap = TEXCOORD_CLAMP;
|
|
112
114
|
clip .reset(-1);
|
|
113
115
|
font = get_default_font();
|
|
114
116
|
texture = Image();
|
|
115
|
-
texcoord_mode = TEXCOORD_IMAGE;
|
|
116
|
-
texcoord_wrap = TEXCOORD_CLAMP;
|
|
117
117
|
shader = Shader();
|
|
118
118
|
}
|
|
119
119
|
|
|
@@ -135,7 +135,7 @@ namespace Rays
|
|
|
135
135
|
return colors[FILL] || colors[STROKE];
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
};//
|
|
138
|
+
};// PainterState
|
|
139
139
|
|
|
140
140
|
|
|
141
141
|
struct TextureInfo
|
|
@@ -177,7 +177,9 @@ namespace Rays
|
|
|
177
177
|
enum Flag
|
|
178
178
|
{
|
|
179
179
|
|
|
180
|
-
PAINTING
|
|
180
|
+
PAINTING = Xot::bit(1, Painter::FLAG_LAST),
|
|
181
|
+
|
|
182
|
+
UNBATCHABLE_STATE_CHANGED = Xot::bit(2, Painter::FLAG_LAST),
|
|
181
183
|
|
|
182
184
|
};// Flag
|
|
183
185
|
|
|
@@ -187,9 +189,9 @@ namespace Rays
|
|
|
187
189
|
|
|
188
190
|
Bounds viewport;
|
|
189
191
|
|
|
190
|
-
|
|
192
|
+
PainterState state;
|
|
191
193
|
|
|
192
|
-
std::vector<
|
|
194
|
+
std::vector<PainterState> state_stack;
|
|
193
195
|
|
|
194
196
|
Matrix position_matrix;
|
|
195
197
|
|
|
@@ -214,8 +216,6 @@ namespace Rays
|
|
|
214
216
|
};// Painter::Data
|
|
215
217
|
|
|
216
218
|
|
|
217
|
-
void Painter_update_clip (Painter* painter);
|
|
218
|
-
|
|
219
219
|
void Painter_flush (Painter* painter);
|
|
220
220
|
|
|
221
221
|
void Painter_draw (
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rays
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.13
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- xordog
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-05-
|
|
11
|
+
date: 2026-05-16 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: xot
|
|
@@ -16,28 +16,28 @@ dependencies:
|
|
|
16
16
|
requirements:
|
|
17
17
|
- - "~>"
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: 0.3.
|
|
19
|
+
version: 0.3.13
|
|
20
20
|
type: :runtime
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
24
|
- - "~>"
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: 0.3.
|
|
26
|
+
version: 0.3.13
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
28
|
name: rucy
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
30
30
|
requirements:
|
|
31
31
|
- - "~>"
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: 0.3.
|
|
33
|
+
version: 0.3.13
|
|
34
34
|
type: :runtime
|
|
35
35
|
prerelease: false
|
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
37
|
requirements:
|
|
38
38
|
- - "~>"
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: 0.3.
|
|
40
|
+
version: 0.3.13
|
|
41
41
|
description: This library helps you to develop graphics application with OpenGL.
|
|
42
42
|
email: xordog@gmail.com
|
|
43
43
|
executables: []
|