ashton 0.0.1alpha

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 (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