ashton 0.0.1alpha

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/CHANGELOG +0 -0
  2. data/LICENSE +21 -0
  3. data/README.md +68 -0
  4. data/Rakefile +24 -0
  5. data/examples/framebuffer_example.rb +50 -0
  6. data/examples/media/Earth.png +0 -0
  7. data/examples/media/LargeStar.png +0 -0
  8. data/examples/media/Star.png +0 -0
  9. data/examples/media/Starfighter.bmp +0 -0
  10. data/examples/media/simple.png +0 -0
  11. data/examples/output/README.txt +1 -0
  12. data/examples/pixelate_example.rb +50 -0
  13. data/examples/radial_blur_example.rb +63 -0
  14. data/examples/shader_image_example.rb +42 -0
  15. data/examples/shockwave2_example.rb +76 -0
  16. data/examples/tv_screen_and_noise_example.rb +60 -0
  17. data/lib/ashton/base_shader.rb +172 -0
  18. data/lib/ashton/framebuffer.rb +183 -0
  19. data/lib/ashton/gosu_ext/color.rb +12 -0
  20. data/lib/ashton/gosu_ext/image.rb +32 -0
  21. data/lib/ashton/gosu_ext/window.rb +36 -0
  22. data/lib/ashton/image_stub.rb +37 -0
  23. data/lib/ashton/include/simplex.glsl +63 -0
  24. data/lib/ashton/post_process/contrast.frag +16 -0
  25. data/lib/ashton/post_process/default.vert +9 -0
  26. data/lib/ashton/post_process/fade.frag +11 -0
  27. data/lib/ashton/post_process/mezzotint.frag +24 -0
  28. data/lib/ashton/post_process/noise.frag +27 -0
  29. data/lib/ashton/post_process/pixelate.frag +48 -0
  30. data/lib/ashton/post_process/radial_blur.frag +31 -0
  31. data/lib/ashton/post_process/sepia.frag +19 -0
  32. data/lib/ashton/post_process/shockwave.frag +40 -0
  33. data/lib/ashton/post_process/shockwave2.frag +35 -0
  34. data/lib/ashton/post_process/tv_screen.frag +32 -0
  35. data/lib/ashton/post_process.rb +83 -0
  36. data/lib/ashton/shader/default.frag +19 -0
  37. data/lib/ashton/shader/default.vert +14 -0
  38. data/lib/ashton/shader.rb +62 -0
  39. data/lib/ashton/version.rb +3 -0
  40. data/lib/ashton.rb +26 -0
  41. metadata +201 -0
data/CHANGELOG ADDED
File without changes
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2012 Bil Bas
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
13
+ all 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
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,68 @@
1
+ Ashton
2
+ ======
3
+
4
+ Description
5
+ -----------
6
+
7
+ Add extra visual effects to the Gosu game-development library, utilising OpenGL shaders (using shader model 1.0, for maximum compatibility) and frame-buffers.
8
+
9
+ "Ashton" is named after [Clark Ashton Smith](http://en.wikipedia.org/wiki/Clark_Ashton_Smith), an fantasy/horror author
10
+ with a particularly colourful imagination.
11
+
12
+ - Author: Bil Bas (Spooner)
13
+ - License: MIT
14
+
15
+ Usage
16
+ -----
17
+
18
+ gem install ashton --pre
19
+
20
+ Features
21
+ --------
22
+
23
+ - Gosu::Font
24
+ * [TODO] #draw - Added :shader hash option to choose optional shader to use.
25
+ * [TODO] #draw_rel - Added :shader hash option to choose optional shader to use.
26
+
27
+ - Gosu::Image
28
+ * #draw - Added :shader hash option to choose optional shader to use.
29
+ * #draw_rot - Added :shader hash option to choose optional shader to use.
30
+ * [TODO] #flip!, #flip, #mirror!, #mirror, #scale!, #scale, etc.
31
+ * [TODO] #resize (Well, create another image which is smaller/larger).
32
+ * [TODO] #to_framebuffer
33
+
34
+ - Gosu::Window
35
+ * #post_process {} - Draws contents of block into a back-buffer, then applies a shader as it draws that onto the screen.
36
+ * [TODO] #to_framebuffer - Copy the contents of the window as a {Ashton::Framebuffer}.
37
+ * [TODO] #to_image - Create Gosu::Image from window contents.
38
+ * [TODO] #draw_line - Added :shader hash option
39
+ * [TODO] #draw_quad - Added :shader hash option
40
+ * [TODO] #draw_triangle - Added :shader hash option
41
+
42
+ - {Ashton::Shader}
43
+ * #use {} - Inside the block, all draw operations are affected by the shader.
44
+ * Supports vertex and fragment shaders.
45
+
46
+ - {Ashton::PostProcess}
47
+ * A specific type of shader to perform full-screen post-processing.
48
+ * #process - Used to post-process the entire Gosu::Window at once, after some, or all, drawing is complete.
49
+ * Supports fragment shaders.
50
+ * [TODO] Includes a small library of example shaders (:blur, :simplex, etc).
51
+
52
+ - {Ashton::Framebuffer}
53
+ * #use {} - Inside the block, draw operations go into the framebuffer, rather than onto the window.
54
+ * #to_image - Convert to Gosu::Image.
55
+ * #draw - Draw directly onto a Gosu::Window.
56
+ * [TODO] #flip!, #flip - Invert framebuffer's vertical orientation.
57
+
58
+ Similar Libraries
59
+ -----------------
60
+
61
+ - [TexPlay](https://github.com/banister/texplay) - Deals with Gosu::Image manipulation, such as per-pixel editing and drawing. It is compatible with, and complementary to, this gem.
62
+
63
+ Credits
64
+ -------
65
+
66
+ - simplex.glsl - simplex noise implementation - Copyright (C) 2011 Ashima Arts - MIT license.
67
+
68
+
data/Rakefile ADDED
@@ -0,0 +1,24 @@
1
+ require 'rake/clean'
2
+ require 'rspec/core/rake_task'
3
+ require 'yard'
4
+ require 'redcloth'
5
+ require 'launchy'
6
+ require 'rubygems/package_task'
7
+
8
+
9
+ spec = Gem::Specification.load Dir['*.gemspec'].first
10
+
11
+ Gem::PackageTask.new spec do
12
+ end
13
+
14
+ YARD::Rake::YardocTask.new
15
+
16
+ task :default => :spec
17
+
18
+ RSpec::Core::RakeTask.new do |t|
19
+ end
20
+
21
+ desc "Open yard docs in browser"
22
+ task :browse_yard => :yard do
23
+ Launchy.open "doc/index.html" rescue nil
24
+ end
@@ -0,0 +1,50 @@
1
+ # Use of GLSL shader in Gosu.
2
+
3
+ begin
4
+ require 'rubygems'
5
+ rescue LoadError
6
+ end
7
+
8
+ $LOAD_PATH.unshift File.expand_path('../lib/', File.dirname(__FILE__))
9
+ require "ashton"
10
+
11
+ def media_path(file); File.expand_path "media/#{file}", File.dirname(__FILE__) end
12
+
13
+ class GameWindow < Gosu::Window
14
+ def initialize
15
+ super 800, 600, false
16
+ self.caption = "Ashton::Framebuffer example - composing an image inside a buffer - hold <LMB> to draw - <delete> to clear"
17
+
18
+ @font = Gosu::Font.new(self, Gosu::default_font_name, 20)
19
+ @star = Gosu::Image.new(self, media_path("LargeStar.png"), true)
20
+ @framebuffer = Ashton::Framebuffer.new width, height
21
+ end
22
+
23
+ def update
24
+ if button_down? Gosu::MsLeft
25
+ # Draw into the framebuffer, rather than onto the screen.
26
+ @framebuffer.use do
27
+ @star.draw_rot mouse_x, mouse_y, 0, 0, 0.5, 0.5
28
+ end
29
+ end
30
+ end
31
+
32
+ def draw
33
+ @framebuffer.draw 0, 0 # Draws immediately.
34
+ @star.draw_rot mouse_x, mouse_y, 0, 0, 0.5, 0.5
35
+
36
+ @font.draw "FPS: #{Gosu::fps}", 0, 0, 0
37
+ end
38
+
39
+ def button_down(id)
40
+ case id
41
+ when Gosu::KbDelete
42
+ @framebuffer.clear
43
+ when Gosu::KbEscape
44
+ close
45
+ end
46
+ end
47
+ end
48
+
49
+ window = GameWindow.new
50
+ window.show
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1 @@
1
+ Output from the examples will be saved here, to prove that some functionality works.
@@ -0,0 +1,50 @@
1
+ # Use of GLSL shader in Gosu to post-process the entire screen.
2
+
3
+ begin
4
+ require 'rubygems'
5
+ rescue LoadError
6
+ end
7
+
8
+ $LOAD_PATH.unshift File.expand_path('../lib/', File.dirname(__FILE__))
9
+ require "ashton"
10
+
11
+ def shader(file); File.read File.expand_path("../lib/ashton/post_process/#{file}", File.dirname(__FILE__)) end
12
+ def media_path(file); File.expand_path "media/#{file}", File.dirname(__FILE__) end
13
+
14
+ class TestWindow < Gosu::Window
15
+ def initialize
16
+ super 640, 480, false
17
+ self.caption = "Post-processing with 'pixelate' - 1..9 affect pixel size"
18
+
19
+ @pixelate = Ashton::PostProcess.new shader('pixelate.frag')
20
+ @pixelate['in_PixelSize'] = @pixel_size = 4
21
+
22
+ @font = Gosu::Font.new self, Gosu::default_font_name, 40
23
+ @star = Gosu::Image.new(self, media_path("LargeStar.png"), true)
24
+
25
+ update # Ensure the values are initially set.
26
+ end
27
+
28
+ def button_down(id)
29
+ if (Gosu::Kb1..Gosu::Kb9).include? id
30
+ @pixel_size = (id - Gosu::Kb1 + 1) ** 2
31
+ @pixelate['in_PixelSize'] = @pixel_size
32
+ elsif id == Gosu::KbEscape
33
+ close
34
+ end
35
+ end
36
+
37
+ def draw
38
+ post_process @pixelate do
39
+ @font.draw_rel "Hello world!", 350, 50, 0, 0.5, 0.5
40
+ @font.draw_rel "Goodbye world!", 400, 350, 0, 0.5, 0.5
41
+ @star.draw 0, 0, 0
42
+ @star.draw 200, 100, 0
43
+ end
44
+
45
+ # Drawing after the effect isn't processed, which is useful for GUI elements.
46
+ @font.draw "Pixel ratio: 1:#{@pixel_size}", 0, 0, 0
47
+ end
48
+ end
49
+
50
+ TestWindow.new.show
@@ -0,0 +1,63 @@
1
+ # Use of GLSL shader in Gosu to post-process the entire screen.
2
+
3
+ begin
4
+ require 'rubygems'
5
+ rescue LoadError
6
+ end
7
+
8
+ $LOAD_PATH.unshift File.expand_path('../lib/', File.dirname(__FILE__))
9
+ require "ashton"
10
+
11
+ def shader(file); File.read File.expand_path("../lib/ashton/post_process/#{file}", File.dirname(__FILE__)) end
12
+
13
+ class TestWindow < Gosu::Window
14
+ def initialize
15
+ super 640, 480, false
16
+ self.caption = "Post-processing with 'radial_blur' - hold space to disable; 1..9 sets blurriness"
17
+
18
+ @blur = Ashton::PostProcess.new shader('radial_blur.frag')
19
+ @blur['in_BrightFactor'] = 1.5
20
+ @blur['in_BlurFactor'] = @blur_factor = 2.0
21
+ @blur['in_Passes'] = 20 # Quite a lot of work, but just proves how fast shader are!
22
+ @font = Gosu::Font.new self, Gosu::default_font_name, 40
23
+
24
+ update # Ensure the values are initially set.
25
+ end
26
+
27
+ def update
28
+ # Wiggle the blur about a bit each frame!
29
+ @blur['in_OriginX'] = mouse_x
30
+ @blur['in_OriginY'] = mouse_y
31
+ end
32
+
33
+ def button_down(id)
34
+ if (Gosu::Kb1..Gosu::Kb9).include? id
35
+ @blur_factor = (id - Gosu::Kb1 + 1).to_f
36
+ @blur['in_BlurFactor'] = @blur_factor
37
+ elsif id == Gosu::KbEscape
38
+ close
39
+ end
40
+ end
41
+
42
+ def draw_scene
43
+ @font.draw_rel "Hello world!", 100, 100, 0, 0, 0.5, 0.5
44
+ @font.draw_rel "Goodbye world!", 400, 280, 0, 0.5, 0.5
45
+
46
+ @font.draw_rel "X", mouse_x, mouse_y, 0, 0.5, 0.5
47
+ end
48
+
49
+ def draw
50
+ if button_down? Gosu::KbSpace
51
+ draw_scene
52
+ else
53
+ post_process @blur do
54
+ draw_scene
55
+ end
56
+ end
57
+
58
+ # Drawing after the effect isn't processed, which is useful for GUI elements.
59
+ @font.draw "Blur Factor: #{@blur_factor}", 0, 0, 0
60
+ end
61
+ end
62
+
63
+ TestWindow.new.show
@@ -0,0 +1,42 @@
1
+ # Use of GLSL shader in Gosu.
2
+
3
+ begin
4
+ require 'rubygems'
5
+ rescue LoadError
6
+ end
7
+
8
+ $LOAD_PATH.unshift File.expand_path('../lib/', File.dirname(__FILE__))
9
+ require "ashton"
10
+
11
+ def media_path(file); File.expand_path "media/#{file}", File.dirname(__FILE__) end
12
+ def output_path(file); File.expand_path "output/#{file}", File.dirname(__FILE__) end
13
+
14
+ class GameWindow < Gosu::Window
15
+ def initialize
16
+ super 640, 480, false
17
+ self.caption = "Gosu & OpenGL Integration Demo (SHADERS)"
18
+
19
+ @font = Gosu::Font.new self, Gosu::default_font_name, 20
20
+ @image = Gosu::Image.new self, media_path("Earth.png"), true
21
+ @shader = Ashton::Shader.new # Just use default shader for now.
22
+ end
23
+
24
+ def draw
25
+ # draw, with and without colour.
26
+ @image.draw 10, 10, 0, :shader => @shader
27
+ @image.draw 10, @image.height + 20, 0, 1, 1, Gosu::Color::RED, :shader => @shader
28
+
29
+ # draw#rot, with and without colour.
30
+ @image.draw_rot 280, 0, 0, 10, 0, 0, :shader => @shader
31
+ @image.draw_rot 280, @image.height + 10, 0, 10, 0, 0, 1, 1, Gosu::Color::RED, :shader => @shader
32
+ end
33
+
34
+ def button_down(id)
35
+ if id == Gosu::KbEscape
36
+ close
37
+ end
38
+ end
39
+ end
40
+
41
+ window = GameWindow.new
42
+ window.show
@@ -0,0 +1,76 @@
1
+ # Use of GLSL shader in Gosu to post-process the entire screen.
2
+
3
+ begin
4
+ require 'rubygems'
5
+ rescue LoadError
6
+ end
7
+
8
+ $LOAD_PATH.unshift File.expand_path('../lib/', File.dirname(__FILE__))
9
+ require "ashton"
10
+
11
+ def shader(file); File.read File.expand_path("../lib/ashton/post_process/#{file}", File.dirname(__FILE__)) end
12
+ def media_path(file); File.expand_path "media/#{file}", File.dirname(__FILE__) end
13
+
14
+ class Shockwave
15
+ def age; Gosu::milliseconds - @start_time; end
16
+
17
+ def process
18
+ @shader.process
19
+ end
20
+
21
+ def initialize(x, y)
22
+ @shader = Ashton::PostProcess.new shader('shockwave2.frag')
23
+ @shader['in_ShockParams'] = [10.0, 0.8, 0.1]
24
+ @shader['in_Center'] = [x, $window.height - y]
25
+ @start_time = Gosu::milliseconds
26
+ end
27
+
28
+ def update
29
+ @shader['in_Time'] = age / 1000.0
30
+ end
31
+ end
32
+
33
+ class TestWindow < Gosu::Window
34
+ def initialize
35
+ super 640, 480, false
36
+ self.caption = "Post-processing with 'shockwave2.frag' - Click on window to create a splash"
37
+
38
+ @font = Gosu::Font.new self, Gosu::default_font_name, 40
39
+ @background = Gosu::Image.new(self, media_path("Earth.png"), true)
40
+ @waves = []
41
+ end
42
+
43
+ def update
44
+ @waves.each {|w| w.update }
45
+ @waves.delete_if {|w| w.age > 3000 }
46
+ end
47
+
48
+ def needs_cursor?
49
+ true
50
+ end
51
+
52
+ def button_down(id)
53
+ case id
54
+ when Gosu::MsLeft
55
+ @waves << Shockwave.new(mouse_x, mouse_y)
56
+ when Gosu::KbEscape
57
+ close
58
+ end
59
+ end
60
+
61
+ def draw
62
+ @background.draw 0, 0, 0, width.fdiv(@background.width), height.fdiv(@background.height)
63
+ @font.draw_rel "Hello world!", 350, 50, 0, 0.5, 0.5
64
+ @font.draw_rel "Goodbye world!", 400, 350, 0, 0.5, 0.5
65
+
66
+ # Since we want to process multiple post-processing effects...
67
+ @waves.each do |wave|
68
+ wave.process
69
+ end
70
+
71
+ # Drawing after the effect isn't processed, which is useful for GUI elements.
72
+ @font.draw "FPS: #{Gosu::fps} Waves: #{@waves.size}", 0, 0, 0
73
+ end
74
+ end
75
+
76
+ TestWindow.new.show
@@ -0,0 +1,60 @@
1
+ # Use of GLSL shader in Gosu to post-process the entire screen.
2
+
3
+ begin
4
+ require 'rubygems'
5
+ rescue LoadError
6
+ end
7
+
8
+ $LOAD_PATH.unshift File.expand_path('../lib/', File.dirname(__FILE__))
9
+ require "ashton"
10
+
11
+ def shader(file); File.read File.expand_path("../lib/ashton/post_process/#{file}", File.dirname(__FILE__)) end
12
+ def media_path(file); File.expand_path "media/#{file}", File.dirname(__FILE__) end
13
+
14
+ class TestWindow < Gosu::Window
15
+ def initialize
16
+ super 640, 480, false
17
+ self.caption = "Post-processing with both 'tv_screen.frag' and 'noise.frag'"
18
+
19
+ @screen = Ashton::PostProcess.new shader('tv_screen.frag')
20
+ @screen['in_ColumnWidth'] = 1.0
21
+
22
+ @noise = Ashton::PostProcess.new shader('noise.frag')
23
+
24
+
25
+ @font = Gosu::Font.new self, Gosu::default_font_name, 40
26
+ @star = Gosu::Image.new(self, media_path("LargeStar.png"), true)
27
+ @background = Gosu::Image.new(self, media_path("Earth.png"), true)
28
+
29
+ update # Ensure the values are initially set.
30
+ end
31
+
32
+ def update
33
+ @noise['in_T'] = Math::sin(Gosu::milliseconds / 500.0) * 1000
34
+ @noise['in_Intensity'] = Math::sin(Gosu::milliseconds / 2345.0) + 1.5
35
+ end
36
+
37
+ def draw
38
+ @background.draw 0, 0, 0, width.fdiv(@background.width), height.fdiv(@background.height)
39
+
40
+ @font.draw_rel "Hello world!", 350, 50, 0, 0.5, 0.5, 1, 1, Gosu::Color::GREEN
41
+ @font.draw_rel "Goodbye world!", 400, 350, 0, 0.5, 0.5, 2, 2, Gosu::Color::BLUE
42
+
43
+ @star.draw 0, 0, 0
44
+ @star.draw 200, 100, 0
45
+
46
+ @noise.process
47
+ @screen.process
48
+
49
+ # Drawing after the effect isn't processed, which is useful for GUI elements.
50
+ @font.draw "FPS: #{Gosu::fps}", 0, 0, 0
51
+ end
52
+
53
+ def button_down(id)
54
+ if id == Gosu::KbEscape
55
+ close
56
+ end
57
+ end
58
+ end
59
+
60
+ TestWindow.new.show
@@ -0,0 +1,172 @@
1
+ module Ashton
2
+ # @abstract
3
+ class BaseShader
4
+ INVALID_LOCATION = -1
5
+ MIN_OPENGL_VERSION = 2.0
6
+
7
+ attr_reader :fragment_source, :vertex_source
8
+
9
+ # Todo: Pass in a filename (String) or name of built-in pp shader (Symbol)
10
+ #
11
+ # @option options [String] :vertex Source code for vertex shader.
12
+ # @option options [String] :vert equivalent to :vertex
13
+ # @option options [String] :fragment Source code for fragment shader.
14
+ # @option options [String] :frag equivalent to :fragment
15
+ def initialize(vertex_source, fragment_source)
16
+ raise "Can't instantiate abstract class" if self.class == BaseShader
17
+
18
+ unless GL.version_supported? MIN_OPENGL_VERSION
19
+ raise NotSupportedError, "Ashton requires OpenGL #{MIN_OPENGL_VERSION} support to utilise shaders"
20
+ end
21
+
22
+ @uniform_locations = {}
23
+ @attribute_locations = {}
24
+ @program = nil
25
+
26
+ @vertex_source = vertex_source
27
+ @fragment_source = fragment_source
28
+
29
+ @vertex = compile GL_VERTEX_SHADER, vertex_source
30
+ @fragment = compile GL_FRAGMENT_SHADER, fragment_source
31
+
32
+ link
33
+
34
+ glBindFragDataLocationEXT @program, 0, "out_FragColor"
35
+ end
36
+
37
+
38
+ # Creates a copy of the shader program, recompiling the source,
39
+ # but not preserving the uniform values.
40
+ def dup
41
+ self.class.new :vertex => @vertex_source, :fragment => @fragment_source
42
+ end
43
+
44
+ # Make this the current shader program.
45
+ def use
46
+ previous_program = glGetIntegerv GL_CURRENT_PROGRAM
47
+ glUseProgram @program
48
+
49
+ if block_given?
50
+ result = yield self
51
+ $window.flush # TODO: need to work out how to make shader affect delayed draws.
52
+ glUseProgram previous_program
53
+ end
54
+
55
+ result
56
+ end
57
+
58
+ # Disable the shader program (not needed in block version of #use).
59
+ def disable
60
+ glUseProgram 0 # Disable the shader!
61
+ end
62
+
63
+ # Is this the current shader program?
64
+ def current?
65
+ glGetIntegerv(GL_CURRENT_PROGRAM) == @program
66
+ end
67
+
68
+ # Set the value of a uniform.
69
+ def []=(name, value)
70
+ use do
71
+ case value
72
+ when true, GL_TRUE
73
+ glUniform1i uniform(name), 1
74
+
75
+ when false, GL_FALSE
76
+ glUniform1i uniform(name), 0
77
+
78
+ when Float
79
+ glUniform1f uniform(name), value
80
+
81
+ when Integer
82
+ glUniform1i uniform(name), value
83
+
84
+ when Array
85
+ size = value.size
86
+
87
+ raise ArgumentError, "Empty array not supported for uniform data" if size.zero?
88
+ raise ArgumentError, "Only support uniforms up to 4 elements" if size > 4
89
+
90
+ case value[0]
91
+ when Float
92
+ GL.send "glUniform#{size}f", uniform(name), *value
93
+
94
+ when Integer
95
+ GL.send "glUniform#{size}i", uniform(name), *value
96
+
97
+ else
98
+ raise ArgumentError, "Uniform data type not supported for element of type: #{value[0].class}"
99
+ end
100
+
101
+ else
102
+ raise ArgumentError, "Uniform data type not supported for type: #{value.class}"
103
+ end
104
+ end
105
+ end
106
+
107
+
108
+
109
+ def uniform(name)
110
+ location = @uniform_locations[name]
111
+ if location
112
+ location
113
+ else
114
+ location = glGetUniformLocation @program, name.to_s
115
+ raise ShaderUniformError, "No #{name} uniform specified in program" if location == INVALID_LOCATION
116
+ @uniform_locations[name] = location
117
+ end
118
+ end
119
+
120
+ protected
121
+ def attribute(name)
122
+ location = @attribute_locations[name]
123
+ if location
124
+ location
125
+ else
126
+ location = glGetAttribLocation @program, name.to_s
127
+ raise ShaderAttributeError, "No #{name} attribute specified in program" if location == INVALID_LOCATION
128
+ @attribute_locations[name] = location
129
+ end
130
+ end
131
+
132
+ protected
133
+ def compile(type, source)
134
+ shader = glCreateShader type
135
+ glShaderSource shader, source
136
+ glCompileShader shader
137
+
138
+ unless glGetShaderiv shader, GL_COMPILE_STATUS
139
+ error = glGetShaderInfoLog shader
140
+ error_lines = error.scan(/0\((\d+)\)+/m).map {|num| num.first.to_i }.uniq
141
+
142
+ if type == GL_VERTEX_SHADER
143
+ type_name = "Vertex"
144
+ source = @vertex_source
145
+ else
146
+ type_name = "Fragment"
147
+ source = @fragment_source
148
+ end
149
+
150
+ source_lines = source.split("\n")
151
+ lines = error_lines.map {|i| "#{i.to_s.rjust 3}: #{source_lines[i - 1].rstrip}" }.join "\n"
152
+ raise ShaderCompileError, "#{type_name} shader error: #{glGetShaderInfoLog(shader)}\n#{lines}"
153
+ end
154
+
155
+ shader
156
+ end
157
+
158
+ protected
159
+ def link
160
+ @program = glCreateProgram
161
+ glAttachShader @program, @vertex
162
+ glAttachShader @program, @fragment
163
+ glLinkProgram @program
164
+
165
+ unless glGetProgramiv @program, GL_LINK_STATUS
166
+ raise ShaderLinkError, "Shader link error: #{glGetProgramInfoLog(@program)}"
167
+ end
168
+
169
+ nil
170
+ end
171
+ end
172
+ end