Class: Ashton::Shader

Inherits:
Object
  • Object
show all
Includes:
Mixins::VersionChecking
Defined in:
lib/ashton/shader.rb

Constant Summary collapse

INVALID_LOCATION =
-1
MIN_OPENGL_VERSION =

For GLSL 1.10

2.0
INCLUDE_PATH =
File.expand_path "../shaders/include", __FILE__
BUILT_IN_SHADER_PATH =
File.expand_path "../shaders", __FILE__
FRAGMENT_EXTENSION =
".frag"
VERTEX_EXTENSION =
".vert"
BUILT_IN_FUNCTIONS =

List of built-in functions.

Dir[File.join(INCLUDE_PATH, "*.glsl")].map do |filename|
  filename =~ /(\w+)\.glsl/
  $1.to_sym
end

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Mixins::VersionChecking

#check_opengl_extension, #check_opengl_version

Constructor Details

#initialize(options = {}) ⇒ Shader

Instead of passing in source code, a file-name will be loaded or use a symbol to choose a built-in shader.

‘#include` will be recursively replaced in the source.

  • ‘#include <noise>` will load the built-in shader function, shaders/include/noise.glsl

  • ‘#include “/home/spooner/noise.glsl”` will include that file, relative to the current working directory, NOT the source file.

Parameters:

  • options (Hash) (defaults to: {})

    a customizable set of options

Options Hash (options):

  • :vertex (String, Symbol) — default: :default

    Source code for vertex shader.

  • :vert (String, Symbol) — default: :default

    Equivalent to :vertex

  • :fragment (String, Symbol) — default: :default

    Source code for fragment shader.

  • :frag (String, Symbol) — default: :default

    Equivalent to :fragment

  • :uniforms (Hash)

    Sets uniforms, as though calling shader = value for each entry (but faster).



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/ashton/shader.rb', line 39

def initialize(options = {})
  check_opengl_version MIN_OPENGL_VERSION

  vertex = options[:vertex] || options[:vert] || :default
  fragment = options[:fragment] || options[:frag] || :default

  @vertex_source = process_source vertex, VERTEX_EXTENSION
  @fragment_source = process_source fragment, FRAGMENT_EXTENSION

  @uniform_locations = {}
  @attribute_locations = {}
  @program = nil
  @previous_program = nil
  @image = nil
  @color = [1, 1, 1, 1]

  # Actually compile and link.

  @vertex = compile Gl::GL_VERTEX_SHADER, @vertex_source
  @fragment = compile Gl::GL_FRAGMENT_SHADER, @fragment_source
  link

  # In case we are using '#version 130' or higher, set out own color output.
  begin
    Gl.glBindFragDataLocationEXT @program, 0, "out_FragColor"
  rescue NotImplementedError
    # Might fail on an old system, but they will be fine just running GLSL 1.10 or 1.20
  end

  enable do
    # GL_TEXTURE0 will be activated later. This is the main image texture.
    set_uniform uniform_location("in_Texture", required: false), 0

    # For multi-textured shaders, we use in_Texture<NUM> instead.
    set_uniform uniform_location("in_Texture0", required: false), 0
    set_uniform uniform_location("in_Texture1", required: false), 1

    # These are optional, and can be used to check pixel size.
    set_uniform uniform_location("in_WindowWidth", required: false), $window.width
    set_uniform uniform_location("in_WindowHeight", required: false), $window.height

    # Set uniform values with :uniforms hash.
    if options.has_key? :uniforms
      options[:uniforms].each_pair do |uniform, value|
        self[uniform] = value
      end
    end
  end
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(meth, *args, &block) ⇒ Object

Allow

`shader.blob_frequency = 5`

to map to

`shader["in_BlobFrequency"] = 5`

TODO: define specific methods at compile time, based on parsing the source?



144
145
146
147
148
149
150
# File 'lib/ashton/shader.rb', line 144

def method_missing(meth, *args, &block)
  if args.size == 1 and meth =~ /^(.+)=$/
    self[$1.to_sym] = args[0]
  else
    super meth, *args, &block
  end
end

Instance Attribute Details

#fragment_sourceObject (readonly)

Returns the value of attribute fragment_source.



19
20
21
# File 'lib/ashton/shader.rb', line 19

def fragment_source
  @fragment_source
end

#vertex_sourceObject (readonly)

Returns the value of attribute vertex_source.



19
20
21
# File 'lib/ashton/shader.rb', line 19

def vertex_source
  @vertex_source
end

Instance Method Details

#[]=(uniform, value) ⇒ Object

Set the value of a uniform.

Parameters:

  • uniform (String, Symbol)

    If a Symbol, :frog_paste is looked up as “in_FrogPaste”, otherwise the Sting is used directly.

  • value (Any)

    Value to set the uniform to

Raises:

  • ShaderUniformError unless requested uniform is defined in vertex or fragment shaders.



159
160
161
162
163
164
165
166
167
168
169
# File 'lib/ashton/shader.rb', line 159

def []=(uniform, value)
  uniform = uniform_name_from_symbol(uniform) if uniform.is_a? Symbol

  # Ensure that the program is current before setting values.
  needs_use = !current?
  enable if needs_use
  set_uniform uniform_location(uniform), value
  disable if needs_use

  value
end

#color=(color) ⇒ Object



272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
# File 'lib/ashton/shader.rb', line 272

def color=(color)
  opengl_color = case color
                   when Gosu::Color
                     color.to_opengl
                   when Integer
                     Gosu::Color.new(color).to_opengl
                   when Array
                     color
                   else
                     raise TypeError, "Expected Gosu::Color, Integer or opengl float array for color"
                 end

  needs_use = !current?
  enable if needs_use
  location = Gl.glGetAttribLocation @program, "in_Color"
  Gl.glVertexAttrib4f location, *opengl_color unless location == INVALID_LOCATION
  disable if needs_use

  @color = opengl_color
end

#current?Boolean

Is this the currently activated shader program?

Returns:

  • (Boolean)


25
# File 'lib/ashton/shader.rb', line 25

def current?; Gl.glGetIntegerv(Gl::GL_CURRENT_PROGRAM) == @program end

#disable(z = nil) ⇒ Object

Disable the shader program. Only required if using #enable without a block.



128
129
130
131
132
133
134
135
136
# File 'lib/ashton/shader.rb', line 128

def disable(z = nil)
  $window.gl z do
    raise ShaderError, "Shader not enabled." unless enabled?
    Gl.glUseProgram @previous_program # Disable the shader!
    @previous_program = nil
  end

  nil
end

#dupObject

Creates a copy of the shader program, recompiling the source, but not preserving the uniform values.



98
99
100
# File 'lib/ashton/shader.rb', line 98

def dup
  self.class.new :vertex => @vertex_source, :fragment => @fragment_source
end

#enable(z = nil) ⇒ Object

Make this the current shader program. Use with a block or, alternatively, use #enable and #disable separately.



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/ashton/shader.rb', line 104

def enable(z = nil)
  $window.gl z do
    raise ShaderError, "This shader already enabled." if enabled?
    current_shader = Gl.glGetIntegerv GL::GL_CURRENT_PROGRAM
    raise ShaderError, "Another shader already enabled." if current_shader > 0

    @previous_program = current_shader
    Gl.glUseProgram @program
  end

  result = nil

  if block_given?
    begin
      result = yield self
    ensure
      disable z
    end
  end

  result
end

#enabled?Boolean

Is the shader currently in use?

Returns:

  • (Boolean)


22
# File 'lib/ashton/shader.rb', line 22

def enabled?; !!@previous_program end

#image=(image) ⇒ Object

Raises:



256
257
258
259
260
261
262
263
264
265
266
267
268
269
# File 'lib/ashton/shader.rb', line 256

def image=(image)
  raise ShaderError, "Can't set image unless using shader" unless current?

  if image
    info = image.gl_tex_info

    Gl.glActiveTexture Gl::GL_TEXTURE0
    Gl.glBindTexture Gl::GL_TEXTURE_2D, info.tex_name
  end

  set_uniform uniform_location("in_TextureEnabled", required: false), !!image

  @image = image
end