electra 0.1.0 → 0.1.1

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.
Files changed (5) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/README.md +122 -49
  4. data/lib/electra/version.rb +1 -1
  5. metadata +7 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5a00586e597777dd9f5a4cef9aa13d7099caeef3dcbc7157616d873da0dd7092
4
- data.tar.gz: 76cd0df41a5da9cc8ca68d349dc56f9b9abb9d951771e167d00fe35f599052bf
3
+ metadata.gz: 6c323b1caf054026764f6565a277a638961d9e490cd6929eb043000602520c9b
4
+ data.tar.gz: 76d8b64b72db2d6fb1c3e999470df1ae371962cc15bd461ccdd0d3a0588e690e
5
5
  SHA512:
6
- metadata.gz: d577fe840555f89644923fd758f3997d05e381f4c2887addc0af1e2ba6c4b07991fda7277e7b3b3c0e2e3173e933f00f2ea8a4b603941d0555e0cffe226550bb
7
- data.tar.gz: 0f3f31f30b13ac2fdcdf15fcda901c191b808990d981b0005dffd5946f8fec4bb1665ce194b7b86df053eed5f5205cb93c6b2238dd84235842c301b0db89c440
6
+ metadata.gz: 976cdfa357990c76839dd0d890f005ffbb7f15442564fb5bc02beae1b3e560955757f4c7d1343dbab96eec03a59d89ecebc63b7e4631de6bc0da86bdb9051609
7
+ data.tar.gz: d994a50d51031e0db82a566d2af2adf8b084f16244b315fff44482473763f20cec73a000906276b8ee65c1bbbc11661b5ccb815de580aa5792c7bc6f35253f6a
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.1 — 2026-09-21
4
+
5
+ - Add a deterministic shader-output demo image and regeneration task.
6
+
3
7
  ## 0.1.0 — 2026-09-11
4
8
 
5
9
  - Initial release.
data/README.md CHANGED
@@ -1,50 +1,98 @@
1
- # Electra
2
-
3
- A pure Ruby SPIR-V shader emitter, typed vertex/fragment DSL, assembler and disassembler.
1
+ <h1 align="center">Electra</h1>
2
+
3
+ <p align="center">
4
+ <strong>Pure Ruby SPIR-V emitter, typed shader DSL, assembler, and disassembler</strong>
5
+ </p>
6
+
7
+ <p align="center">
8
+ <a href="https://rubygems.org/gems/electra"><img src="https://img.shields.io/gem/v/electra.svg" alt="Gem version"></a>
9
+ <a href="https://rubygems.org/gems/electra"><img src="https://img.shields.io/gem/dt/electra.svg" alt="Gem downloads"></a>
10
+ <img src="https://img.shields.io/badge/ruby-%3E%3D%203.1-ruby.svg" alt="Ruby version">
11
+ <a href="https://github.com/noxdea/electra/actions/workflows/main.yml"><img src="https://github.com/noxdea/electra/actions/workflows/main.yml/badge.svg" alt="CI status"></a>
12
+ <a href="LICENSE.txt"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT license"></a>
13
+ </p>
14
+
15
+ <p align="center">
16
+ <a href="#features">Features</a> ·
17
+ <a href="#installation">Installation</a> ·
18
+ <a href="#quick-start">Quick Start</a> ·
19
+ <a href="#shader-dsl">Shader DSL</a> ·
20
+ <a href="#assembly-and-low-level-api">Assembly</a> ·
21
+ <a href="#development">Development</a>
22
+ </p>
23
+
24
+ ---
4
25
 
5
26
  Electra builds the small shaders a UI renderer needs directly from Ruby, without running GLSL compilers or loading native bindings. Use it when a Vulkan application needs to ship shader construction as Ruby source. It is not a GLSL compiler, optimizer, or complete SPIR-V semantic validator.
6
27
 
7
- Requires Ruby 3.1+. No runtime gem dependencies; the library also works with `ruby --disable-gems`. SPIRV-Tools are optional development oracles, never runtime requirements.
28
+ ![Electra shader output](docs/media/screenshot.png)
8
29
 
9
- ## Try it
30
+ ## Features
10
31
 
11
- ```ruby
12
- require "electra"
13
- mod = Electra::Module.new
14
- mod.fragment_shader { |f| f.output(:vec4, location: 0).store(f.constant([1, 0, 0, 1])) }
15
- binary = mod.to_binary
16
- puts Electra.disassemble(binary)
32
+ - Typed vertex and fragment shader DSL for UI renderers
33
+ - Pure Ruby with no runtime gem dependencies
34
+ - SPIR-V 1.0–1.6 binary emission
35
+ - Numeric-ID assembler and disassembler with byte-for-byte round trips
36
+ - Explicit uniform, push-constant, array, and matrix layouts
37
+ - Grammar tables derived from a pinned Khronos SPIRV-Headers revision
38
+
39
+ ## Installation
40
+
41
+ Add Electra to your bundle:
42
+
43
+ ```sh
44
+ bundle add electra
17
45
  ```
18
46
 
19
- Install the gem:
47
+ Or install it directly:
20
48
 
21
49
  ```sh
22
50
  gem install electra
23
51
  ```
24
52
 
25
- Or build it from this checkout:
53
+ ### Requirements
54
+
55
+ - Ruby 3.1 or newer
56
+ - SPIRV-Tools is optional and only used for development validation
57
+
58
+ ## Quick Start
59
+
60
+ Build a fragment shader and inspect its SPIR-V assembly:
61
+
62
+ ```ruby
63
+ require "electra"
64
+
65
+ mod = Electra::Module.new
66
+ mod.fragment_shader do |f|
67
+ output = f.output(:vec4, location: 0)
68
+ output.store(f.constant([1, 0, 0, 1]))
69
+ end
70
+
71
+ binary = mod.to_binary
72
+ puts Electra.disassemble(binary)
73
+ ```
74
+
75
+ Generate the bundled triangle and text shader examples from a checkout:
26
76
 
27
77
  ```sh
28
- gem build electra.gemspec
29
- gem install --local electra-0.1.0.gem
30
78
  ruby -Ilib examples/example_shaders.rb tmp/shaders
31
79
  ```
32
80
 
33
- The example emits a no-vertex-buffer triangle pair and a text fragment shader. `Electra::ExampleShaders.triangle_vertex` uses `VertexIndex`, so the consumer can issue `vkCmdDraw(3, 1, 0, 0)`. Both entry points are named `main`; the fragment output is location 0.
81
+ The triangle vertex shader uses `VertexIndex`, so the consumer can issue `vkCmdDraw(3, 1, 0, 0)` without a vertex buffer. Both entry points are named `main`; the fragment output is location 0.
34
82
 
35
- ## Text shader
83
+ ## Shader DSL
84
+
85
+ This texture shader samples an alpha channel and multiplies it by a vertex tint:
36
86
 
37
87
  ```ruby
38
88
  mod = Electra::Module.new(version: "1.0", generator: 0)
39
- mod.capability :Shader
40
- mod.memory_model :Logical, :GLSL450
41
89
 
42
90
  mod.fragment_shader("main") do |f|
43
91
  uv = f.input(:vec2, location: 0)
44
92
  tint = f.input(:vec4, location: 1)
45
- tex = f.sampled_image_2d(set: 0, binding: 0)
93
+ texture = f.sampled_image_2d(set: 0, binding: 0)
46
94
  output = f.output(:vec4, location: 0)
47
- alpha = f.sample(tex, uv).component(:r)
95
+ alpha = f.sample(texture, uv).component(:r)
48
96
  output.store(f.mul(tint, f.splat(alpha)))
49
97
  end
50
98
 
@@ -52,20 +100,20 @@ binary = mod.to_binary
52
100
  raise "round trip failed" unless Electra.assemble(Electra.disassemble(binary)) == binary
53
101
  ```
54
102
 
55
- `Shader`, `Logical/GLSL450`, `OriginUpperLeft` for fragments, and function return instructions are added automatically. Calling `capability` again is harmless. `to_binary` returns a little-endian binary String, with an exact ID bound and schema zero. Versions 1.0–1.6 are accepted; the application must select a version supported by its Vulkan environment. SPIR-V 1.4+ descriptor/push-constant variables are included in entry-point interfaces.
103
+ `Shader`, `Logical/GLSL450`, `OriginUpperLeft` for fragments, and function return instructions are added automatically. `to_binary` returns a little-endian binary String with an exact ID bound and schema zero. The application must select a SPIR-V version supported by its Vulkan environment. SPIR-V 1.4+ descriptor and push-constant variables are included in entry-point interfaces.
56
104
 
57
- ## Types and operations
105
+ ### Types and operations
58
106
 
59
- Scalar types are `:bool`, `:int`, `:uint`, `:float` (32-bit numbers). Vectors are `:vec2`–`:vec4`, and `:ivecN`, `:uvecN`, `:bvecN`. Matrices `:mat2`–`:mat4` and `:matCxR` are column-major floating-point matrices. `mod.array(type, count, stride:)` and `mod.struct(*types, offsets:, block:)` expose explicit aggregate layout. Struct/matrix constants use nested arrays, one array per column.
107
+ Scalar types are `:bool`, `:int`, `:uint`, and `:float` (32-bit numbers). Vectors are `:vec2`–`:vec4`, `:ivecN`, `:uvecN`, and `:bvecN`. Matrices `:mat2`–`:mat4` and `:matCxR` are column-major floating-point matrices. `mod.array(type, count, stride:)` and `mod.struct(*types, offsets:, block:)` expose explicit aggregate layout. Struct and matrix constants use nested arrays, one array per column.
60
108
 
61
- Types are structurally interned; constants are interned by their type and exact bit pattern, including negative zero. `f.constant(value, type)` creates a typed constant. Numeric literals passed alongside a typed operand use its type; use `1.0` for an inferred float and `1` for an inferred signed integer.
109
+ Types are structurally interned; constants are interned by type and exact bit pattern, including negative zero. `f.constant(value, type)` creates a typed constant. Numeric literals passed alongside a typed operand use its type; use `1.0` for an inferred float and `1` for an inferred signed integer.
62
110
 
63
111
  | Operation | API |
64
112
  | --- | --- |
65
113
  | Inputs/outputs | `input(type, location:)`, `output(type, location:)`; alternatively `builtin: :Position` / `:VertexIndex`; input `flat: true` |
66
114
  | Components/composites | `value.component(:r)` / `component(0)`, `construct(type, *components)`, `splat(scalar, count = 4)` |
67
115
  | Arithmetic | `add`, `sub`, `mul`, `div`, `mod`, `negate`; scalar/vector numeric types |
68
- | Matrices | `mul` selects matrix×matrix, matrix×vector, vector×matrix or scalar multiplication; `transpose`, `dot` |
116
+ | Matrices | `mul` selects matrix×matrix, matrix×vector, vector×matrix, or scalar multiplication; `transpose`, `dot` |
69
117
  | Conversion | `convert(value, type)` for numeric scalar/vector conversion; int/uint conversion is bit-preserving |
70
118
  | Comparison | `equal`, `not_equal`, `less_than`, `less_equal`, `greater_than`, `greater_equal` |
71
119
  | Boolean/selection | `logical_and`, `logical_or`, `logical_not`, `select(condition, yes, no)` |
@@ -75,6 +123,8 @@ Types are structurally interned; constants are interned by their type and exact
75
123
 
76
124
  Inputs and descriptors load automatically when consumed; output variables use `store`. Types and values cannot cross module boundaries, and function-local values cannot cross function boundaries. Integer fragment inputs should be declared `flat: true` as required by Vulkan.
77
125
 
126
+ ### Structured control flow
127
+
78
128
  Structured branches allocate forward labels and merge matching returned values with `OpPhi`:
79
129
 
80
130
  ```ruby
@@ -86,45 +136,58 @@ adjusted = f.if_else(f.less_than(alpha, 0.5),
86
136
  f.output(:vec4, location: 0).store(f.splat(adjusted))
87
137
  ```
88
138
 
89
- ## Uniform and push-constant layout
139
+ ### Uniform and push-constant layout
90
140
 
91
- Offsets/strides are explicit: the emitter does not guess a CPU/GPU ABI.
141
+ Offsets and strides are explicit: the emitter does not guess a CPU/GPU ABI.
92
142
 
93
143
  ```ruby
94
- buffer = f.uniform_buffer(:mat4, set: 0, binding: 0, offsets: [0])
95
- mod.member_decorate(buffer.type, 0, :ColMajor)
96
- mod.member_decorate(buffer.type, 0, :MatrixStride, 16)
97
- position.store(f.mul(f.member(buffer, 0), input_position))
98
- # f.push_constant(:vec4, offsets: [0]) returns a read-only struct variable.
144
+ mod.vertex_shader do |f|
145
+ input = f.input(:vec4, location: 0)
146
+ output = f.output(:vec4, builtin: :Position)
147
+ buffer = f.uniform_buffer(:mat4, set: 0, binding: 0, offsets: [0])
148
+ push = f.push_constant(:vec4, offsets: [0])
149
+
150
+ mod.member_decorate(buffer.type, 0, :ColMajor)
151
+ mod.member_decorate(buffer.type, 0, :MatrixStride, 16)
152
+ output.store(f.add(f.mul(f.member(buffer, 0), input), f.member(push, 0)))
153
+ end
99
154
  ```
100
155
 
101
- `decorate(target, decoration, *values)` supports the grammar's decorations, including Location, Binding, DescriptorSet, Block and ArrayStride. `member_decorate(type, index, decoration, *values)` handles Offset and matrix layout. Repeating the same decoration is harmless; conflicting values raise `Electra::Error`.
156
+ `decorate(target, decoration, *values)` supports the grammar's decorations, including Location, Binding, DescriptorSet, Block, and ArrayStride. `member_decorate(type, index, decoration, *values)` handles Offset and matrix layout. Repeating the same decoration is harmless; conflicting values raise `Electra::Error`.
157
+
158
+ ## Assembly and low-level API
159
+
160
+ `Electra.disassemble(binary)` validates the header, instruction framing, operands, strings, and ID bounds. `Electra.assemble(text)` accepts numeric `%123` IDs and SPIR-V instruction syntax. Output from the disassembler round-trips byte-for-byte, preserving version, generator, bound, and endianness through header comments. Floating-point hexadecimal spelling preserves 16/32/64-bit constants, signed zero, and NaN payloads.
102
161
 
103
- For instructions outside the DSL, `mod.reserve_id` / `mod.ref(:label)` reserve forward IDs, and `mod.emit(section, "OpName", id, "name")` accepts grammar-order operands. Sections are emitted in specification order: capabilities, extensions, imports, memory model, entry points, execution modes, debug, annotations, declarations, functions. Every reserved ID must be defined exactly once. The low-level API does not validate dominance, capability requirements or storage-layout rules: run `spirv-val`.
162
+ For instructions outside the DSL, `mod.reserve_id` / `mod.ref(:label)` reserve forward IDs, and `mod.emit(section, "OpName", id, "name")` accepts grammar-order operands. Sections are emitted in specification order. Every reserved ID must be defined exactly once. The low-level API does not validate dominance, capability requirements, or storage-layout rules: run `spirv-val`.
104
163
 
105
- ## Assembly and limits
164
+ The opcode, enum, and GLSL.std.450 tables are derived from a pinned [Khronos SPIRV-Headers revision](https://github.com/KhronosGroup/SPIRV-Headers/tree/496543121ce6419f23d6fa5d7194ba66c36212d2/include/spirv/unified1).
106
165
 
107
- `Electra.disassemble(binary)` validates the header, instruction framing, operands, strings and ID bounds. `Electra.assemble(text)` accepts numeric `%123` IDs and SPIR-V instruction syntax; output from this disassembler round-trips byte-for-byte, preserving version, generator, bound and endianness through header comments. Strings use SPIR-V escaping (only quote/backslash need escaping; embedded line breaks remain literal). Floating-point hexadecimal spelling preserves 16/32/64-bit constants, signed zero and NaN payloads.
166
+ ### Supported scope and limits
108
167
 
109
- The complete opcode/enum table and GLSL.std.450 names are derived from a pinned [Khronos SPIRV-Headers revision](https://github.com/KhronosGroup/SPIRV-Headers/tree/496543121ce6419f23d6fa5d7194ba66c36212d2/include/spirv/unified1). The typed DSL intentionally covers UI vertex and fragment shaders only. Compute, tessellation, geometry, subgroups, ray tracing, GLSL parsing, optimization and GPU resource management are not implemented. The assembler/disassembler is not a general replacement for SPIRV-Tools: nonnumeric ID names, other extended-instruction symbolic vocabularies, and context-dependent 64-bit `OpSwitch` literals are outside its supported round-trip surface.
168
+ The typed DSL intentionally covers UI vertex and fragment shaders only. Compute, tessellation, geometry, subgroups, ray tracing, GLSL parsing, optimization, and GPU resource management are not implemented.
110
169
 
111
- Errors are `Electra::Error`; builders should be discarded after a failed mutation. No binary is claimed valid merely because it can be disassembled. GPU limits and full shader semantics are checked by the actual Vulkan implementation and, in development, `spirv-val`.
170
+ The assembler and disassembler are not a general replacement for SPIRV-Tools. Nonnumeric ID names, other extended-instruction symbolic vocabularies, and context-dependent 64-bit `OpSwitch` literals are outside the supported round-trip surface. No binary is claimed valid merely because it can be disassembled; the Vulkan implementation and, during development, `spirv-val` check full shader semantics and GPU limits.
112
171
 
113
- Module/Function objects own mutable construction state; use one builder per thread. Type/Value records and generated grammar tables are frozen. Independent builders share no allocation counters or shader state.
172
+ Errors are reported as `Electra::Error`; discard a builder after a failed mutation. Module and function objects own mutable construction state, so use one builder per thread. Type and value records and generated grammar tables are frozen. Independent builders share no allocation counters or shader state.
114
173
 
115
- ## Verification and performance
174
+ ## Development
116
175
 
117
176
  ```sh
118
177
  bundle install
119
- bundle exec rake # tests and isolated install
178
+ bundle exec rake # tests and isolated install
120
179
  bundle exec rake test:oracle # requires SPIRV-Tools; skips if absent
121
- BUDGET=1 bundle exec rake bench # 10ms regression guard, not a frame-time target
180
+ BUDGET=1 bundle exec rake bench # 10 ms regression guard
122
181
  rbs -I sig validate
123
182
  ```
124
183
 
125
- The independent tests include malformed/truncated binaries, 1,000 seeded binary mutations, type/constant interning, forward references, exact assembly round trips, float boundaries and strings. When installed, `spirv-val`, `spirv-dis` and `spirv-as` check complete emitted instruction streams, including comparisons, nested branches/phi/discard, matrix operations, buffer layout, explicit LOD and GLSL intrinsics across SPIR-V 1.0/1.3/1.4/1.5/1.6. Linux CI installs these tools; macOS/Windows skip only external oracles when unavailable. A Vulkan draw/readback test belongs to the renderer consuming the shared triangle shader pair; this library's SPIRV-Tools tests alone do not prove a rendered image.
184
+ Tests cover malformed and truncated binaries, 1,000 seeded binary mutations, type and constant interning, forward references, exact assembly round trips, float boundaries, and strings. When installed, `spirv-val`, `spirv-dis`, and `spirv-as` validate complete emitted instruction streams across SPIR-V 1.0, 1.3, 1.4, 1.5, and 1.6. Linux CI requires these external checks; macOS and Windows skip them when the tools are unavailable.
185
+
186
+ A Vulkan draw/readback test belongs to the renderer consuming the shared triangle shaders. SPIRV-Tools tests alone do not prove a rendered image.
187
+
188
+ ### Performance
126
189
 
127
- Measured locally on arm64 macOS, Ruby 4.0.0 + YJIT; 604-byte text fragment, median of five runs of 1,000 operations:
190
+ Measured locally on arm64 macOS with Ruby 4.0.0 and YJIT; 604-byte text fragment, median of five runs of 1,000 operations:
128
191
 
129
192
  | Workload | Time | Allocated objects |
130
193
  | --- | ---: | ---: |
@@ -132,10 +195,20 @@ Measured locally on arm64 macOS, Ruby 4.0.0 + YJIT; 604-byte text fragment, medi
132
195
  | Disassemble | 108.82 µs | 959 |
133
196
  | Assemble | 205.05 µs | 1,653 |
134
197
 
135
- Shaders are intended to be built once and retained, not rebuilt per frame. Rerun `bench/emitter.rb` on the target Ruby/platform; these are measurements, not universal guarantees.
198
+ Shaders are intended to be built once and retained, not rebuilt per frame. Rerun `bench/emitter.rb` on the target Ruby and platform; these are measurements, not universal guarantees.
199
+
200
+ ## Contributing
201
+
202
+ Bug reports and pull requests are welcome at <https://github.com/noxdea/electra>.
203
+
204
+ ## Name
205
+
206
+ Electra is a named star in the Pleiades. This library uses the name for its
207
+ precise SPIR-V instruction engraving. The RubyGems name is `electra`; the Ruby
208
+ namespace is `Electra`.
136
209
 
137
- ## Name and license
210
+ ## License
138
211
 
139
- Electra is the engraving tool that cuts precise lines into metal: this library similarly engraves SPIR-V instructions. The RubyGems name is `electra`, namespace `Electra`.
212
+ Electra is released under the [MIT License](LICENSE.txt). Khronos grammar and derived tables retain their [upstream license](LICENSE-SPIRV-Headers.txt).
140
213
 
141
- MIT; see [LICENSE.txt](LICENSE.txt). Khronos grammar and derived tables retain their [upstream license](LICENSE-SPIRV-Headers.txt). Protocol references: [SPIR-V registry](https://registry.khronos.org/SPIR-V/), [Vulkan SPIR-V environment](https://docs.vulkan.org/spec/latest/appendices/spirvenv.html), [GLSL.std.450](https://registry.khronos.org/SPIR-V/specs/unified1/GLSL.std.450.html).
214
+ Protocol references: [SPIR-V registry](https://registry.khronos.org/SPIR-V/), [Vulkan SPIR-V environment](https://docs.vulkan.org/spec/latest/appendices/spirvenv.html), and [GLSL.std.450](https://registry.khronos.org/SPIR-V/specs/unified1/GLSL.std.450.html).
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Electra
4
4
  # Library semantic version (independent from SPIR-V module versions).
5
- VERSION = "0.1.0"
5
+ VERSION = "0.1.1"
6
6
  end
metadata CHANGED
@@ -1,14 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: electra
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yudai Takada
8
+ autorequire:
8
9
  bindir: exe
9
10
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
11
+ date: 2026-09-21 00:00:00.000000000 Z
11
12
  dependencies: []
13
+ description:
12
14
  email:
13
15
  - t.yudai92@gmail.com
14
16
  executables: []
@@ -38,6 +40,7 @@ metadata:
38
40
  changelog_uri: https://github.com/noxdea/electra/blob/main/CHANGELOG.md
39
41
  allowed_push_host: https://rubygems.org
40
42
  rubygems_mfa_required: 'true'
43
+ post_install_message:
41
44
  rdoc_options: []
42
45
  require_paths:
43
46
  - lib
@@ -52,7 +55,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
52
55
  - !ruby/object:Gem::Version
53
56
  version: '0'
54
57
  requirements: []
55
- rubygems_version: 4.0.19
58
+ rubygems_version: 3.4.19
59
+ signing_key:
56
60
  specification_version: 4
57
61
  summary: A pure Ruby SPIR-V shader emitter and disassembler
58
62
  test_files: []