Class: CyberarmEngine::Shader

Inherits:
Object
  • Object
show all
Includes:
OpenGL
Defined in:
lib/cyberarm_engine/shader.rb

Overview

Constant Summary collapse

PREPROCESSOR_CHARACTER =

magic character for preprocessor phase of CyberarmEngine::Shader compilation

"@".freeze
@@shaders =

Cache for CyberarmEngine::Shader instances

{}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name:, includes_dir: nil, vertex: "shaders/default.vert", fragment:) ⇒ Shader

Returns a new instance of Shader.



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/cyberarm_engine/shader.rb', line 111

def initialize(name:, includes_dir: nil, vertex: "shaders/default.vert", fragment:)
  raise "Shader name can not be blank" if name.length == 0

  @name = name
  @includes_dir = includes_dir
  @compiled = false

  @program = nil

  @error_buffer_size = 1024 * 8
  @variable_missing = {}

  @data = {shaders: {}}

  unless shader_files_exist?(vertex: vertex, fragment: fragment)
    raise ArgumentError, "Shader files not found: #{vertex} or #{fragment}"
  end

  create_shader(type: :vertex, source: File.read(vertex))
  create_shader(type: :fragment, source: File.read(fragment))

  compile_shader(type: :vertex)
  compile_shader(type: :fragment)
  link_shaders

  @data[:shaders].each { |key, id| glDeleteShader(id) }

  # Only add shader if it successfully compiles
  if @compiled
    puts "compiled!"
    puts "Compiled shader: #{@name}"
    Shader.add(@name, self)
  else
    glDeleteProgram(@program)
    warn "FAILED to compile shader: #{@name}", ""
  end
end

Instance Attribute Details

#nameObject (readonly)

Returns the value of attribute name.



110
111
112
# File 'lib/cyberarm_engine/shader.rb', line 110

def name
  @name
end

#programObject (readonly)

Returns the value of attribute program.



110
111
112
# File 'lib/cyberarm_engine/shader.rb', line 110

def program
  @program
end

Class Method Details

.active_shaderShader?

returns currently active CyberarmEngine::Shader, if one is active

Returns:



71
72
73
# File 'lib/cyberarm_engine/shader.rb', line 71

def self.active_shader
  @active_shader
end

.active_shader=(instance) ⇒ Object

sets currently active CyberarmEngine::Shader

Parameters:



78
79
80
# File 'lib/cyberarm_engine/shader.rb', line 78

def self.active_shader=(instance)
  @active_shader = instance
end

.add(name, instance) ⇒ Object

add instance of CyberarmEngine::Shader to cache

Parameters:

  • name (String)
  • instance (Shader)


12
13
14
# File 'lib/cyberarm_engine/shader.rb', line 12

def self.add(name, instance)
  @@shaders[name] = instance
end

.attribute_location(variable) ⇒ Object

returns location of OpenGL Shader uniform

Parameters:

  • variable (String)

Raises:

  • (RuntimeError)


96
97
98
99
# File 'lib/cyberarm_engine/shader.rb', line 96

def self.attribute_location(variable)
  raise RuntimeError, "No active shader!" unless Shader.active_shader
  Shader.active_shader.attribute_location(variable)
end

.available?(name) ⇒ Boolean

returns whether CyberarmEngine::Shader with name is in cache

Parameters:

  • name (String)

Returns:

  • (Boolean)


56
57
58
# File 'lib/cyberarm_engine/shader.rb', line 56

def self.available?(name)
  @@shaders.dig(name).is_a?(Shader)
end

.delete(name) ⇒ Object

removes CyberarmEngine::Shader from cache and cleans up

Parameters:

  • name (String)


19
20
21
22
23
24
25
26
27
28
29
# File 'lib/cyberarm_engine/shader.rb', line 19

def self.delete(name)
  shader = @@shaders.dig(name)

  if shader
    @@shaders.delete(name)

    if shader.compiled?
      glDeleteProgram(shader.program)
    end
  end
end

.get(name) ⇒ Shader?

returns instance of CyberarmEngine::Shader, if it exists

Parameters:

  • name (String)

Returns:



64
65
66
# File 'lib/cyberarm_engine/shader.rb', line 64

def self.get(name)
  @@shaders.dig(name)
end

.set_uniform(variable, value) ⇒ Object

sets variable to value

Parameters:

  • variable (String)
  • value

Raises:

  • (RuntimeError)


105
106
107
108
# File 'lib/cyberarm_engine/shader.rb', line 105

def self.set_uniform(variable, value)
  raise RuntimeError, "No active shader!" unless Shader.active_shader
  Shader.active_shader.set_uniform(variable, value)
end

.stopObject

stops using currently active CyberarmEngine::Shader



83
84
85
86
87
88
89
90
91
# File 'lib/cyberarm_engine/shader.rb', line 83

def self.stop
  shader = Shader.active_shader

  if shader
    shader.stop
  else
    raise ArgumentError, "No active shader to stop!"
  end
end

.use(name, &block) ⇒ void

This method returns an undefined value.

runs block using CyberarmEngine::Shader with name

Examples:


CyberarmEngine::Shader.use("blur") do |shader|
  shader.uniform_float("radius", 20.0)
  # OpenGL Code that uses shader
end

Parameters:



43
44
45
46
47
48
49
50
# File 'lib/cyberarm_engine/shader.rb', line 43

def self.use(name, &block)
  shader = @@shaders.dig(name)
  if shader
    shader.use(&block)
  else
    raise ArgumentError, "Shader '#{name}' not found!"
  end
end

Instance Method Details

#attribute_location(variable) ⇒ Integer

Note:

Use #variable for friendly error handling

returns location of a uniform variable

Parameters:

  • variable (String)

Returns:

  • (Integer)

See Also:



323
324
325
# File 'lib/cyberarm_engine/shader.rb', line 323

def attribute_location(variable)
  glGetUniformLocation(@program, variable)
end

#compile_shader(type:) ⇒ Boolean

compile OpenGL Shader of type

Returns:

  • (Boolean)

    whether compilation succeeded

Raises:

  • (ArgumentError)


227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
# File 'lib/cyberarm_engine/shader.rb', line 227

def compile_shader(type:)
  _compiled = false
  _shader = @data[:shaders][type]
  raise ArgumentError, "No shader for #{type.inspect}" unless _shader

  glCompileShader(_shader)
  buffer = '    '
  glGetShaderiv(_shader, GL_COMPILE_STATUS, buffer)
  compiled = buffer.unpack('L')[0]

  if compiled == 0
    log = ' ' * @error_buffer_size
    glGetShaderInfoLog(_shader, @error_buffer_size, nil, log)
    puts "Shader Error: Program \"#{@name}\""
    puts "  #{type.to_s.capitalize} Shader InfoLog:", "  #{log.strip.split("\n").join("\n  ")}\n\n"
    puts "  Shader Compiled status: #{compiled}"
    puts "    NOTE: assignment of uniforms in shaders is illegal!"
    puts
  else
    _compiled = true
  end

  return _compiled
end

#compiled?Boolean

Returns whether CyberarmEngine::Shader successfully compiled.

Returns:



312
313
314
# File 'lib/cyberarm_engine/shader.rb', line 312

def compiled?
  @compiled
end

#create_shader(type:, source:) ⇒ Object

creates an OpenGL Shader of type using source

Parameters:

  • type (Symbol)

    valid values are: :vertex, :fragment

  • source (String)

    source code for shader



160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/cyberarm_engine/shader.rb', line 160

def create_shader(type:, source:)
  _shader = nil

  case type
  when :vertex
    _shader = glCreateShader(GL_VERTEX_SHADER)
  when :fragment
    _shader = glCreateShader(GL_FRAGMENT_SHADER)
  else
    raise ArgumentError, "Unsupported shader type: #{type.inspect}"
  end

  processed_source = preprocess_source(source: source)

  _source = [processed_source].pack("p")
  _size = [processed_source.length].pack("I")
  glShaderSource(_shader, 1, _source, _size)

  @data[:shaders][type] =_shader
end
Note:

linking must succeed or shader cannot be used

link compiled OpenGL Shaders in to a OpenGL Program

Returns:

  • (Boolean)

    whether linking succeeded



257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
# File 'lib/cyberarm_engine/shader.rb', line 257

def link_shaders
  @program = glCreateProgram
  @data[:shaders].values.each do |_shader|
    glAttachShader(@program, _shader)
  end
  glLinkProgram(@program)

  buffer = '    '
  glGetProgramiv(@program, GL_LINK_STATUS, buffer)
  linked = buffer.unpack('L')[0]

  if linked == 0
    log = ' ' * @error_buffer_size
    glGetProgramInfoLog(@program, @error_buffer_size, nil, log)
    puts "Shader Error: Program \"#{@name}\""
    puts "  Program InfoLog:", "  #{log.strip.split("\n").join("\n  ")}\n\n"
  end

  @compiled = linked == 0 ? false : true
end

#preprocess_source(source:) ⇒ Object

evaluates shader preprocessors

currently supported preprocessors:

@include "file/path" "another/file/path" # => Replace line with contents of file; Shader includes_dir must be specified in constructor

Examples:

# Example Vertex Shader #
# #version 330 core
# @include "material_struct"
# void main() {
#   gl_Position = vec4(1, 1, 1, 1);
# }

Shader.new(name: "model_renderer", includes_dir: "path/to/includes", vertex: "path/to/vertex_shader.glsl")

Parameters:

  • source

    shader source code



198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/cyberarm_engine/shader.rb', line 198

def preprocess_source(source:)
  lines = source.lines

  lines.each_with_index do |line, i|
    if line.start_with?(PREPROCESSOR_CHARACTER)
      preprocessor = line.strip.split(" ")
      lines.delete(line)

      case preprocessor.first
      when "@include"
        raise ArgumentError, "Shader preprocessor include directory was not given for shader #{@name}" unless @includes_dir

        preprocessor[1..preprocessor.length - 1].join.scan(/"([^"]*)"/).flatten.each do |file|
          source = File.read("#{@includes_dir}/#{file}.glsl")

          lines.insert(i, source)
        end
      else
        warn "Unsupported preprocessor #{preprocessor.first} for #{@name}"
      end
    end
  end

  lines.join
end

#shader_files_exist?(vertex:, fragment:) ⇒ Boolean

whether vertex and fragment files exist on disk

Returns:

  • (Boolean)


152
153
154
# File 'lib/cyberarm_engine/shader.rb', line 152

def shader_files_exist?(vertex:, fragment:)
  File.exist?(vertex) && File.exist?(fragment)
end

#stopObject

stop using shader, if shader is active



306
307
308
309
# File 'lib/cyberarm_engine/shader.rb', line 306

def stop
  Shader.active_shader = nil if Shader.active_shader == self
  glUseProgram(0)
end

#uniform_boolean(variable, value, location = nil) ⇒ void

This method returns an undefined value.

send Boolean to CyberarmEngine::Shader

Parameters:

  • variable (String)
  • value (Boolean)
  • location (Integer) (defaults to: nil)


345
346
347
348
349
# File 'lib/cyberarm_engine/shader.rb', line 345

def uniform_boolean(variable, value, location = nil)
  attr_loc = location ? location : attribute_location(variable)

  glUniform1i(attr_loc, value ? 1 : 0)
end

#uniform_float(variable, value, location = nil) ⇒ void

This method returns an undefined value.

send Float to CyberarmEngine::Shader

Parameters:

  • variable (String)
  • value (Float)
  • location (Integer) (defaults to: nil)


368
369
370
371
372
# File 'lib/cyberarm_engine/shader.rb', line 368

def uniform_float(variable, value, location = nil)
  attr_loc = location ? location : attribute_location(variable)

  glUniform1f(attr_loc, value)
end

#uniform_integer(variable, value, location = nil) ⇒ void

This method returns an undefined value.

send Integer to CyberarmEngine::Shader

Parameters:

  • variable (String)
  • value (Integer)
  • location (Integer) (defaults to: nil)


356
357
358
359
360
# File 'lib/cyberarm_engine/shader.rb', line 356

def uniform_integer(variable, value, location = nil)
  attr_loc = location ? location : attribute_location(variable)

  glUniform1i(attr_loc, value)
end

#uniform_transform(variable, value, location = nil) ⇒ void

This method returns an undefined value.

send Transform to CyberarmEngine::Shader

Parameters:

  • variable (String)
  • value (Transform)
  • location (Integer) (defaults to: nil)


333
334
335
336
337
# File 'lib/cyberarm_engine/shader.rb', line 333

def uniform_transform(variable, value, location = nil)
  attr_loc = location ? location : attribute_location(variable)

  glUniformMatrix4fv(attr_loc, 1, GL_FALSE, value.to_gl.pack("F16"))
end

#uniform_vec3(variable, value, location = nil) ⇒ void

This method returns an undefined value.

send Vector (x, y, z) to CyberarmEngine::Shader

Parameters:

  • variable (String)
  • value (Vector)
  • location (Integer) (defaults to: nil)


380
381
382
383
384
# File 'lib/cyberarm_engine/shader.rb', line 380

def uniform_vec3(variable, value, location = nil)
  attr_loc = location ? location : attribute_location(variable)

  glUniform3f(attr_loc, *value.to_a[0..2])
end

#uniform_vec4(variable, value, location = nil) ⇒ void

This method returns an undefined value.

send Vector to CyberarmEngine::Shader

Parameters:

  • variable (String)
  • value (Vector)
  • location (Integer) (defaults to: nil)


392
393
394
395
396
# File 'lib/cyberarm_engine/shader.rb', line 392

def uniform_vec4(variable, value, location = nil)
  attr_loc = location ? location : attribute_location(variable)

  glUniform4f(attr_loc, *value.to_a)
end

#use(&block) ⇒ Object

See Also:



292
293
294
295
296
297
298
299
300
301
302
303
# File 'lib/cyberarm_engine/shader.rb', line 292

def use(&block)
  return unless compiled?
  raise "Another shader is already in use! #{Shader.active_shader.name.inspect}" if Shader.active_shader
  Shader.active_shader=self

  glUseProgram(@program)

  if block
    block.call(self)
    stop
  end
end

#variable(variable) ⇒ Integer

Returns the location of a uniform variable

Parameters:

  • variable (String)

Returns:

  • (Integer)

    location of uniform



282
283
284
285
286
287
288
289
# File 'lib/cyberarm_engine/shader.rb', line 282

def variable(variable)
  loc = glGetUniformLocation(@program, variable)
  if (loc == -1)
    puts "Shader Error: Program \"#{@name}\" has no such uniform named \"#{variable}\"", "  Is it used in the shader? GLSL may have optimized it out.", "  Is it miss spelled?" unless @variable_missing[variable]
    @variable_missing[variable] = true
  end
  return loc
end