danabr75-ashton 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (111) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG +0 -0
  3. data/LICENSE +21 -0
  4. data/README.md +95 -0
  5. data/Rakefile +42 -0
  6. data/examples/bloom_example.rb +59 -0
  7. data/examples/lighting_example.rb +127 -0
  8. data/examples/media/Earth.png +0 -0
  9. data/examples/media/LargeStar.png +0 -0
  10. data/examples/media/SmallStar.png +0 -0
  11. data/examples/media/Star.png +0 -0
  12. data/examples/media/Starfighter.bmp +0 -0
  13. data/examples/media/Starfighter.png +0 -0
  14. data/examples/media/simple.png +0 -0
  15. data/examples/noise_example.rb +94 -0
  16. data/examples/outline_example.rb +86 -0
  17. data/examples/particle_emitter_example.rb +114 -0
  18. data/examples/pixelate_example.rb +52 -0
  19. data/examples/pixelated_texture_example.rb +69 -0
  20. data/examples/radial_blur_example.rb +61 -0
  21. data/examples/shader_image_example.rb +75 -0
  22. data/examples/shockwave_example.rb +75 -0
  23. data/examples/stencil_shader_example.rb +104 -0
  24. data/examples/texture_render_example.rb +54 -0
  25. data/examples/tv_screen_and_static_example.rb +60 -0
  26. data/ext/ashton/GLee.c +18170 -0
  27. data/ext/ashton/GLee.h +17648 -0
  28. data/ext/ashton/ashton.c +42 -0
  29. data/ext/ashton/ashton.h +31 -0
  30. data/ext/ashton/color.c +45 -0
  31. data/ext/ashton/color.h +25 -0
  32. data/ext/ashton/common.h +41 -0
  33. data/ext/ashton/extconf.rb +45 -0
  34. data/ext/ashton/fast_math.c +30 -0
  35. data/ext/ashton/fast_math.h +30 -0
  36. data/ext/ashton/font.c +8 -0
  37. data/ext/ashton/font.h +16 -0
  38. data/ext/ashton/gosu.c +18 -0
  39. data/ext/ashton/gosu.h +19 -0
  40. data/ext/ashton/image.c +8 -0
  41. data/ext/ashton/image.h +16 -0
  42. data/ext/ashton/particle_emitter.c +788 -0
  43. data/ext/ashton/particle_emitter.h +171 -0
  44. data/ext/ashton/pixel_cache.c +237 -0
  45. data/ext/ashton/pixel_cache.h +58 -0
  46. data/ext/ashton/shader.c +9 -0
  47. data/ext/ashton/shader.h +16 -0
  48. data/ext/ashton/texture.c +442 -0
  49. data/ext/ashton/texture.h +63 -0
  50. data/ext/ashton/vendor/gl/include/GL/GL.H +1526 -0
  51. data/ext/ashton/window.c +8 -0
  52. data/ext/ashton/window.h +16 -0
  53. data/lib/ashton.rb +38 -0
  54. data/lib/ashton/gosu_ext/color.rb +25 -0
  55. data/lib/ashton/gosu_ext/font.rb +58 -0
  56. data/lib/ashton/gosu_ext/gosu_module.rb +16 -0
  57. data/lib/ashton/gosu_ext/image.rb +96 -0
  58. data/lib/ashton/gosu_ext/window.rb +79 -0
  59. data/lib/ashton/image_stub.rb +33 -0
  60. data/lib/ashton/lighting/light_source.rb +146 -0
  61. data/lib/ashton/lighting/manager.rb +98 -0
  62. data/lib/ashton/mixins/version_checking.rb +23 -0
  63. data/lib/ashton/particle_emitter.rb +87 -0
  64. data/lib/ashton/pixel_cache.rb +24 -0
  65. data/lib/ashton/shader.rb +386 -0
  66. data/lib/ashton/shaders/bloom.frag +41 -0
  67. data/lib/ashton/shaders/color_inversion.frag +11 -0
  68. data/lib/ashton/shaders/contrast.frag +16 -0
  69. data/lib/ashton/shaders/default.frag +22 -0
  70. data/lib/ashton/shaders/default.vert +14 -0
  71. data/lib/ashton/shaders/fade.frag +14 -0
  72. data/lib/ashton/shaders/grayscale.frag +15 -0
  73. data/lib/ashton/shaders/include/classicnoise2d.glsl +113 -0
  74. data/lib/ashton/shaders/include/classicnoise3d.glsl +177 -0
  75. data/lib/ashton/shaders/include/classicnoise4d.glsl +302 -0
  76. data/lib/ashton/shaders/include/noise2d.glsl +70 -0
  77. data/lib/ashton/shaders/include/noise3d.glsl +102 -0
  78. data/lib/ashton/shaders/include/noise4d.glsl +128 -0
  79. data/lib/ashton/shaders/include/rand.glsl +5 -0
  80. data/lib/ashton/shaders/lighting/distort.frag +57 -0
  81. data/lib/ashton/shaders/lighting/draw_shadows.frag +60 -0
  82. data/lib/ashton/shaders/lighting/shadow_blur.frag +60 -0
  83. data/lib/ashton/shaders/mezzotint.frag +22 -0
  84. data/lib/ashton/shaders/multitexture2.vert +19 -0
  85. data/lib/ashton/shaders/outline.frag +45 -0
  86. data/lib/ashton/shaders/pixelate.frag +48 -0
  87. data/lib/ashton/shaders/radial_blur.frag +63 -0
  88. data/lib/ashton/shaders/sepia.frag +26 -0
  89. data/lib/ashton/shaders/shockwave.frag +38 -0
  90. data/lib/ashton/shaders/signed_distance_field.frag +80 -0
  91. data/lib/ashton/shaders/static.frag +25 -0
  92. data/lib/ashton/shaders/stencil.frag +27 -0
  93. data/lib/ashton/shaders/tv_screen.frag +23 -0
  94. data/lib/ashton/signed_distance_field.rb +151 -0
  95. data/lib/ashton/texture.rb +186 -0
  96. data/lib/ashton/version.rb +3 -0
  97. data/lib/ashton/window_buffer.rb +16 -0
  98. data/spec/ashton/ashton_spec.rb +22 -0
  99. data/spec/ashton/gosu_ext/color_spec.rb +34 -0
  100. data/spec/ashton/gosu_ext/font_spec.rb +57 -0
  101. data/spec/ashton/gosu_ext/gosu_spec.rb +11 -0
  102. data/spec/ashton/gosu_ext/image_spec.rb +66 -0
  103. data/spec/ashton/gosu_ext/window_spec.rb +71 -0
  104. data/spec/ashton/image_stub_spec.rb +46 -0
  105. data/spec/ashton/particle_emitter_spec.rb +123 -0
  106. data/spec/ashton/pixel_cache_spec.rb +153 -0
  107. data/spec/ashton/shader_spec.rb +152 -0
  108. data/spec/ashton/signed_distance_field_spec.rb +163 -0
  109. data/spec/ashton/texture_spec.rb +347 -0
  110. data/spec/helper.rb +12 -0
  111. metadata +309 -0
@@ -0,0 +1,98 @@
1
+ require 'set'
2
+
3
+ module Ashton
4
+ module Lighting
5
+ # Based on Catalin Zima's shader based dynamic shadows system.
6
+ # http://www.catalinzima.com/2010/07/my-technique-for-the-shader-based-dynamic-2d-shadows/
7
+ class Manager
8
+ include Enumerable
9
+
10
+ attr_accessor :camera_x, :camera_y, :z
11
+
12
+ def each(&block); @lights.each(&block) end
13
+ def size; @lights.size end
14
+ def empty?; @lights.empty? end
15
+ def width; @shadows.width end
16
+ def height; @shadows.height end
17
+
18
+ def initialize(options = {})
19
+ options = {
20
+ width: $window.width,
21
+ height: $window.height,
22
+ camera_x: 0,
23
+ camera_y: 0,
24
+ z: 0,
25
+ }.merge! options
26
+
27
+ @camera_x, @camera_y = options[:camera_x], options[:camera_y]
28
+ @z = options[:z]
29
+
30
+ @lights = Set.new
31
+ @shadows = Ashton::Texture.new options[:width], options[:height]
32
+ end
33
+
34
+ # @param light [Ashton::LightSource]
35
+ # @return [Ashton::LightSource]
36
+ def add(light)
37
+ raise TypeError unless light.is_a? LightSource
38
+
39
+ @lights << light
40
+ light
41
+ end
42
+ alias_method :<<, :add
43
+
44
+ def remove(light)
45
+ @lights -= [light]
46
+ light
47
+ end
48
+
49
+ # @see Ashton::LightSource#new
50
+ #
51
+ # @return [Ashton::LightSource]
52
+ def create_light(*args)
53
+ add LightSource.new(*args)
54
+ end
55
+
56
+ def draw(options = {})
57
+ options = {
58
+ mode: :multiply,
59
+ }.merge! options
60
+
61
+ @shadows.draw @camera_x, @camera_y, @z, options
62
+ end
63
+
64
+ def update_shadow_casters(&block)
65
+ raise ArgumentError, "Requires block" unless block_given?
66
+
67
+ unless empty?
68
+ # TODO: Need to only render to lights that are on-screen.
69
+ @lights.each do |light|
70
+ light.send :render_shadow_casters, &block
71
+ end
72
+
73
+ # Use each shader on every light, to save setting and un-setting shaders (a bit faster, depending on number of light sources).
74
+ LightSource.distort_shader.enable do
75
+ @lights.each {|light| light.send :distort }
76
+ end
77
+
78
+ LightSource.draw_shadows_shader.enable do
79
+ @lights.each {|light| light.send :draw_shadows }
80
+ end
81
+
82
+ LightSource.blur_shader.enable do
83
+ @lights.each {|light| light.send :blur }
84
+ end
85
+ end
86
+
87
+ @shadows.render do |buffer|
88
+ buffer.clear
89
+ $window.translate(-@camera_x, -@camera_y) do
90
+ @lights.each {|light| light.draw } unless empty?
91
+ end
92
+ end
93
+
94
+ nil
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,23 @@
1
+ module Ashton
2
+ module Mixins
3
+ module VersionChecking
4
+ # Check if a specific OpenGL version is supported on this machine.
5
+ #
6
+ # @raise NotSupportedError
7
+ def check_opengl_version(version)
8
+ unless GL.version_supported? version
9
+ raise NotSupportedError, "OpenGL #{version} required to utilise #{self.class}"
10
+ end
11
+ end
12
+
13
+ # Check if a specific OpenGL extension is supported on this machine.
14
+ #
15
+ # @raise NotSupportedError
16
+ def check_opengl_extension(extension)
17
+ unless GL.extension_supported? extension
18
+ raise NotSupportedError, "OpenGL extension #{extension} required to utilise #{self.class}"
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,87 @@
1
+ module Ashton
2
+ class ParticleEmitter
3
+ def empty?; count == 0 end
4
+
5
+ DEFAULT_MAX_PARTICLES = 1000
6
+ DEFAULT_COLOR = Gosu::Color::WHITE
7
+ RANGED_ATTRIBUTES = [
8
+ :angular_velocity, :center_x, :center_y,
9
+ :fade, :friction, :interval,
10
+ :offset, :scale, :speed, :time_to_live, :zoom
11
+ ]
12
+
13
+ def initialize(x, y, z, options = {})
14
+ # I'm MUCH too lazy to implement a huge options hash manager in C, especially on a constructor.
15
+ max_particles = options[:max_particles] || DEFAULT_MAX_PARTICLES
16
+ initialize_ x, y, z, max_particles
17
+
18
+ self.shader = options[:shader]
19
+ self.image = options[:image] || $window.pixel
20
+
21
+ self.gravity = options[:gravity] || 0.0
22
+ self.color = options[:color] || DEFAULT_COLOR
23
+
24
+ self.angular_velocity = options[:angular_velocity] || 0.0
25
+ self.center_x = options[:center_x] || 0.5
26
+ self.center_y = options[:center_y] || 0.5
27
+ self.fade = options[:fade] || 0.0
28
+ self.friction = options[:friction] || 0.0
29
+ self.interval = options[:interval] || Float::INFINITY
30
+ self.offset = options[:offset] || 0.0
31
+ self.scale = options[:scale] || 1.0
32
+ self.speed = options[:speed] || 0.0
33
+ self.time_to_live = options[:time_to_live] || Float::INFINITY
34
+ self.zoom = options[:zoom] || 0.0
35
+ end
36
+
37
+ # Gosu::Color
38
+ def color
39
+ Gosu::Color.new color_argb
40
+ end
41
+
42
+ # [Gosu::Color, Integer, Array<Float>]
43
+ def color=(value)
44
+ case value
45
+ when Integer
46
+ self.color_argb = value
47
+ when Gosu::Color
48
+ self.color_argb = value.to_i
49
+ when Array
50
+ self.color_argb = Gosu::Color.from_opengl value
51
+ else
52
+ raise TypeError, "Expected argb integer, rgba opengl float array or Gosu::Color"
53
+ end
54
+
55
+ value
56
+ end
57
+
58
+ RANGED_ATTRIBUTES.each do |attr|
59
+ # Returns a Range.
60
+ define_method attr do
61
+ send("#{attr}_min")..send("#{attr}_max")
62
+ end
63
+
64
+ # Can be set as a Range or as a single number.
65
+ define_method "#{attr}=" do |value|
66
+ min, max = case value
67
+ when Numeric
68
+ [value, value]
69
+ when Range
70
+ [value.min, value.max]
71
+ else
72
+ raise TypeError, "Expecting Numeric or Range, not #{value.class}"
73
+ end
74
+
75
+ send "#{attr}_min=", min
76
+ send "#{attr}_max=", max
77
+
78
+ value
79
+ end
80
+ end
81
+
82
+ # @!method draw()
83
+
84
+ # @!method update(delta)
85
+ # @param delta (Float) number of seconds to run the simulation for.
86
+ end
87
+ end
@@ -0,0 +1,24 @@
1
+ module Ashton
2
+ class PixelCache
3
+ # Docs here.
4
+
5
+ public
6
+ # Convert the current contents of the cache into a Gosu::Image
7
+ #
8
+ # @option options :caching [Boolean] (true) TexPlay behaviour.
9
+ # @option options :tileable [Boolean] (false) Standard Gosu behaviour.
10
+ def to_image(options = {})
11
+ options = {
12
+ tileable: false,
13
+ }.merge! options
14
+
15
+ # Create a new Image from the flipped pixel data.
16
+ stub = ImageStub.new to_blob, width, height
17
+ if defined? TexPlay
18
+ Gosu::Image.new $window, stub, options[:tileable], options
19
+ else
20
+ Gosu::Image.new $window, stub, options[:tileable]
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,386 @@
1
+ module Ashton
2
+ class Shader
3
+ include Mixins::VersionChecking
4
+
5
+ INVALID_LOCATION = -1
6
+ MIN_OPENGL_VERSION = 2.0 # For GLSL 1.10
7
+
8
+ INCLUDE_PATH = File.expand_path "../shaders/include", __FILE__
9
+ BUILT_IN_SHADER_PATH = File.expand_path "../shaders", __FILE__
10
+ FRAGMENT_EXTENSION = ".frag"
11
+ VERTEX_EXTENSION = ".vert"
12
+
13
+ # List of built-in functions.
14
+ BUILT_IN_FUNCTIONS = Dir[File.join(INCLUDE_PATH, "*.glsl")].map do |filename|
15
+ filename =~ /(\w+)\.glsl/
16
+ $1.to_sym
17
+ end
18
+
19
+ attr_reader :vertex_source, :fragment_source
20
+
21
+ # Is the shader currently in use?
22
+ def enabled?; !!@previous_program end
23
+
24
+ # Is this the currently activated shader program?
25
+ def current?; Gl.glGetIntegerv(Gl::GL_CURRENT_PROGRAM) == @program end
26
+
27
+ # Instead of passing in source code, a file-name will be loaded or use a symbol to choose a built-in shader.
28
+ #
29
+ # `#include` will be recursively replaced in the source.
30
+ #
31
+ # * `#include <noise>` will load the built-in shader function, shaders/include/noise.glsl
32
+ # * `#include "/home/spooner/noise.glsl"` will include that file, relative to the current working directory, NOT the source file.
33
+ #
34
+ # @option options :vertex [String, Symbol] (:default) Source code for vertex shader.
35
+ # @option options :vert [String, Symbol] (:default) Equivalent to :vertex
36
+ # @option options :fragment [String, Symbol] (:default) Source code for fragment shader.
37
+ # @option options :frag [String, Symbol] (:default) Equivalent to :fragment
38
+ # @option options :uniforms [Hash] Sets uniforms, as though calling shader[key] = value for each entry (but faster).
39
+ def initialize(options = {})
40
+ check_opengl_version MIN_OPENGL_VERSION
41
+
42
+ vertex = options[:vertex] || options[:vert] || :default
43
+ fragment = options[:fragment] || options[:frag] || :default
44
+
45
+ @vertex_source = process_source vertex, VERTEX_EXTENSION
46
+ @fragment_source = process_source fragment, FRAGMENT_EXTENSION
47
+
48
+ @uniform_locations = {}
49
+ @attribute_locations = {}
50
+ @program = nil
51
+ @previous_program = nil
52
+ @image = nil
53
+ @color = [1, 1, 1, 1]
54
+
55
+ # Actually compile and link.
56
+
57
+ @vertex = compile Gl::GL_VERTEX_SHADER, @vertex_source
58
+ @fragment = compile Gl::GL_FRAGMENT_SHADER, @fragment_source
59
+ link
60
+
61
+ # In case we are using '#version 130' or higher, set out own color output.
62
+ begin
63
+ Gl.glBindFragDataLocationEXT @program, 0, "out_FragColor"
64
+ rescue NotImplementedError
65
+ # Might fail on an old system, but they will be fine just running GLSL 1.10 or 1.20
66
+ end
67
+
68
+ enable do
69
+ # GL_TEXTURE0 will be activated later. This is the main image texture.
70
+ set_uniform uniform_location("in_Texture", required: false), 0
71
+
72
+ # For multi-textured shaders, we use in_Texture<NUM> instead.
73
+ set_uniform uniform_location("in_Texture0", required: false), 0
74
+ set_uniform uniform_location("in_Texture1", required: false), 1
75
+
76
+ # These are optional, and can be used to check pixel size.
77
+ set_uniform uniform_location("in_WindowWidth", required: false), $window.width
78
+ set_uniform uniform_location("in_WindowHeight", required: false), $window.height
79
+
80
+ # Set uniform values with :uniforms hash.
81
+ if options.has_key? :uniforms
82
+ options[:uniforms].each_pair do |uniform, value|
83
+ self[uniform] = value
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ protected
90
+ # Converts :frog_head to "in_FrogHead"
91
+ def uniform_name_from_symbol(uniform)
92
+ "in_#{uniform.to_s.split("_").map(&:capitalize).join}"
93
+ end
94
+
95
+ public
96
+ # Creates a copy of the shader program, recompiling the source,
97
+ # but not preserving the uniform values.
98
+ def dup
99
+ self.class.new :vertex => @vertex_source, :fragment => @fragment_source
100
+ end
101
+
102
+ public
103
+ # Make this the current shader program. Use with a block or, alternatively, use #enable and #disable separately.
104
+ def enable(z = nil)
105
+ $window.gl z do
106
+ raise ShaderError, "This shader already enabled." if enabled?
107
+ current_shader = Gl.glGetIntegerv GL::GL_CURRENT_PROGRAM
108
+ raise ShaderError, "Another shader already enabled." if current_shader > 0
109
+
110
+ @previous_program = current_shader
111
+ Gl.glUseProgram @program
112
+ end
113
+
114
+ result = nil
115
+
116
+ if block_given?
117
+ begin
118
+ result = yield self
119
+ ensure
120
+ disable z
121
+ end
122
+ end
123
+
124
+ result
125
+ end
126
+
127
+ # Disable the shader program. Only required if using #enable without a block.
128
+ def disable(z = nil)
129
+ $window.gl z do
130
+ raise ShaderError, "Shader not enabled." unless enabled?
131
+ Gl.glUseProgram @previous_program # Disable the shader!
132
+ @previous_program = nil
133
+ end
134
+
135
+ nil
136
+ end
137
+
138
+ public
139
+ # Allow
140
+ # `shader.blob_frequency = 5`
141
+ # to map to
142
+ # `shader["in_BlobFrequency"] = 5`
143
+ # TODO: define specific methods at compile time, based on parsing the source?
144
+ def method_missing(meth, *args, &block)
145
+ if args.size == 1 and meth =~ /^(.+)=$/
146
+ self[$1.to_sym] = args[0]
147
+ else
148
+ super meth, *args, &block
149
+ end
150
+ end
151
+
152
+ public
153
+ # Set the value of a uniform.
154
+ #
155
+ # @param uniform [String, Symbol] If a Symbol, :frog_paste is looked up as "in_FrogPaste", otherwise the Sting is used directly.
156
+ # @param value [Any] Value to set the uniform to
157
+ #
158
+ # @raise ShaderUniformError unless requested uniform is defined in vertex or fragment shaders.
159
+ def []=(uniform, value)
160
+ uniform = uniform_name_from_symbol(uniform) if uniform.is_a? Symbol
161
+
162
+ # Ensure that the program is current before setting values.
163
+ needs_use = !current?
164
+ enable if needs_use
165
+ set_uniform uniform_location(uniform), value
166
+ disable if needs_use
167
+
168
+ value
169
+ end
170
+
171
+ protected
172
+ # Set uniform without trying to force use of the program.
173
+ def set_uniform(location, value)
174
+ raise ShaderUniformError, "Shader uniform #{location.inspect} could not be set, since shader is not current" unless current?
175
+
176
+ return if location == INVALID_LOCATION # Not for end-users :)
177
+
178
+ case value
179
+ when true, Gl::GL_TRUE
180
+ Gl.glUniform1i location, 1
181
+
182
+ when false, Gl::GL_FALSE
183
+ Gl.glUniform1i location, 0
184
+
185
+ when Float
186
+ begin
187
+ Gl.glUniform1f location, value
188
+ rescue
189
+ Gl.glUniform1i location, value.to_i
190
+ end
191
+
192
+ when Integer
193
+ begin
194
+ Gl.glUniform1i location, value
195
+ rescue
196
+ Gl.glUniform1f location, value.to_f
197
+ end
198
+
199
+ when Gosu::Color
200
+ Gl.glUniform4f location, *value.to_opengl
201
+
202
+ when Array
203
+ size = value.size
204
+
205
+ raise ArgumentError, "Empty array not supported for uniform data" if size.zero?
206
+ # raise ArgumentError, "Only support uniforms up to 4 elements" if size > 4
207
+
208
+ case value[0]
209
+ when Float
210
+ begin
211
+ Gl.send "glUniform#{size}f", location, *value.map(&:to_f)
212
+ rescue
213
+ Gl.send "glUniform#{size}i", location, *value.map(&:to_i)
214
+ end
215
+
216
+ when Integer
217
+ begin
218
+ Gl.send "glUniform#{size}i", location, *value.map(&:to_i)
219
+ rescue
220
+ Gl.send "glUniform#{size}f", location, *value.map(&:to_f)
221
+ end
222
+
223
+ when Gosu::Color
224
+ GL.send "glUniform4fv", location, value.map(&:to_opengl).flatten
225
+
226
+ else
227
+ raise ArgumentError, "Uniform data type not supported for element of type: #{value[0].class}"
228
+ end
229
+
230
+ else
231
+ raise ArgumentError, "Uniform data type not supported for type: #{value.class}"
232
+ end
233
+
234
+ value
235
+ end
236
+
237
+ protected
238
+ def uniform_location(name, options = {})
239
+ options = {
240
+ required: true
241
+ }.merge! options
242
+
243
+ location = @uniform_locations[name]
244
+ if location
245
+ location
246
+ else
247
+ location = Gl.glGetUniformLocation @program, name.to_s
248
+ if options[:required] && location == INVALID_LOCATION
249
+ raise ShaderUniformError, "No #{name.inspect} uniform specified in program"
250
+ end
251
+ @uniform_locations[name] = location
252
+ end
253
+ end
254
+
255
+ public
256
+ def image=(image)
257
+ raise ShaderError, "Can't set image unless using shader" unless current?
258
+
259
+ if image
260
+ info = image.gl_tex_info
261
+
262
+ Gl.glActiveTexture Gl::GL_TEXTURE0
263
+ Gl.glBindTexture Gl::GL_TEXTURE_2D, info.tex_name
264
+ end
265
+
266
+ set_uniform uniform_location("in_TextureEnabled", required: false), !!image
267
+
268
+ @image = image
269
+ end
270
+
271
+ public
272
+ def color=(color)
273
+ opengl_color = case color
274
+ when Gosu::Color
275
+ color.to_opengl
276
+ when Integer
277
+ Gosu::Color.new(color).to_opengl
278
+ when Array
279
+ color
280
+ else
281
+ raise TypeError, "Expected Gosu::Color, Integer or opengl float array for color"
282
+ end
283
+
284
+ needs_use = !current?
285
+ enable if needs_use
286
+ location = Gl.glGetAttribLocation @program, "in_Color"
287
+ Gl.glVertexAttrib4f location, *opengl_color unless location == INVALID_LOCATION
288
+ disable if needs_use
289
+
290
+ @color = opengl_color
291
+ end
292
+
293
+ protected
294
+ def attribute(name)
295
+ location = @attribute_locations[name]
296
+ if location
297
+ location
298
+ else
299
+ location = Gl.glGetAttribLocation @program, name.to_s
300
+ raise ShaderAttributeError, "No #{name} attribute specified in program" if location == INVALID_LOCATION
301
+ @attribute_locations[name] = location
302
+ end
303
+ end
304
+
305
+ protected
306
+ def compile(type, source)
307
+ shader = Gl.glCreateShader type
308
+ Gl.glShaderSource shader, source
309
+ Gl.glCompileShader shader
310
+
311
+ unless Gl.glGetShaderiv shader, Gl::GL_COMPILE_STATUS
312
+ error = Gl.glGetShaderInfoLog shader
313
+ error_lines = error.scan(/0\((\d+)\)+/m).map {|num| num.first.to_i }.uniq
314
+
315
+ if type == Gl::GL_VERTEX_SHADER
316
+ type_name = "Vertex"
317
+ source = @vertex_source
318
+ else
319
+ type_name = "Fragment"
320
+ source = @fragment_source
321
+ end
322
+
323
+ source_lines = source.split("\n")
324
+ lines = error_lines.map {|i| "#{i.to_s.rjust 3}: #{source_lines[i - 1].rstrip}" }.join "\n"
325
+ raise ShaderCompileError, "#{type_name} shader error: #{glGetShaderInfoLog(shader)}\n#{lines}"
326
+ end
327
+
328
+ shader
329
+ end
330
+
331
+ protected
332
+ def link
333
+ @program = Gl.glCreateProgram
334
+ Gl.glAttachShader @program, @vertex
335
+ Gl.glAttachShader @program, @fragment
336
+ Gl.glLinkProgram @program
337
+
338
+ unless Gl.glGetProgramiv @program, Gl::GL_LINK_STATUS
339
+ raise ShaderLinkError, "Shader link error: #{glGetProgramInfoLog(@program)}"
340
+ end
341
+
342
+ nil
343
+ end
344
+
345
+ protected
346
+ # Symbol => load a built-in
347
+ # Filename => load file
348
+ # Source => use directly.
349
+ #
350
+ # Also recursively replaces #include
351
+ # TODO: What about line numbers getting messed up by #include?
352
+ def process_source(shader, extension)
353
+ source = if shader.is_a? Symbol
354
+ file = File.expand_path "#{shader}#{extension}", BUILT_IN_SHADER_PATH
355
+ unless File.exist? file
356
+ raise ShaderLoadError, "Failed to load built-in shader: #{shader.inspect}"
357
+ end
358
+ File.read file
359
+
360
+ elsif File.exist? shader
361
+ File.read shader
362
+ else
363
+ shader
364
+ end
365
+
366
+ replace_include source
367
+ end
368
+
369
+ protected
370
+ # Recursively replace #include.
371
+ #
372
+ # * Replace '#include <rand>' with the contents of include/rand.glsl
373
+ # * Replace '#include "/home/spooner/my_shader_functions/frog.glsl"' with the contents of that file.
374
+ #
375
+ # @return [String] Source code that has been expanded.
376
+ def replace_include(source)
377
+ source.gsub!(/^#include\s+<([^>]*)>/) do
378
+ replace_include File.read(File.expand_path("#{$1}.glsl", INCLUDE_PATH))
379
+ end
380
+
381
+ source.gsub(/^#include\s+"([^"]*)"/) do
382
+ replace_include File.read($1)
383
+ end
384
+ end
385
+ end
386
+ end