Class: CyberarmEngine::Shader

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

Overview

Constant Summary collapse

PREPROCESSOR_CHARACTER =

magic character for preprocessor phase of CyberarmEngine::Shader compilation

"@".freeze
@@shaders =

Cache for CyberarmEngine::Shader instances

{}

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

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

Returns a new instance of Shader.



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
148
# File 'lib/cyberarm_engine/opengl/shader.rb', line 112

def initialize(name:, fragment:, includes_dir: nil, vertex: "shaders/default.vert")
  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

Class Attribute Details

.active_shaderObject

Returns the value of attribute active_shader.



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

def active_shader
  @active_shader
end

Instance Attribute Details

#nameObject (readonly)

Returns the value of attribute name.



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

def name
  @name
end

#programObject (readonly)

Returns the value of attribute program.



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

def program
  @program
end

Class Method Details

.add(name, instance) ⇒ Object

add instance of CyberarmEngine::Shader to cache

Parameters:

  • name (String)
  • instance (Shader)


12
13
14
# File 'lib/cyberarm_engine/opengl/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)


94
95
96
97
98
# File 'lib/cyberarm_engine/opengl/shader.rb', line 94

def self.attribute_location(variable)
  raise "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)


54
55
56
# File 'lib/cyberarm_engine/opengl/shader.rb', line 54

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
# File 'lib/cyberarm_engine/opengl/shader.rb', line 19

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

  if shader
    @@shaders.delete(name)

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

.get(name) ⇒ Shader?

returns instance of CyberarmEngine::Shader, if it exists

Parameters:

  • name (String)

Returns:



62
63
64
# File 'lib/cyberarm_engine/opengl/shader.rb', line 62

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

.set_uniform(variable, value) ⇒ Object

sets variable to value

Parameters:

  • variable (String)
  • value


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

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

  Shader.active_shader.set_uniform(variable, value)
end

.stopObject

stops using currently active CyberarmEngine::Shader



81
82
83
84
85
86
87
88
89
# File 'lib/cyberarm_engine/opengl/shader.rb', line 81

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:



41
42
43
44
45
46
47
48
# File 'lib/cyberarm_engine/opengl/shader.rb', line 41

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:



331
332
333
# File 'lib/cyberarm_engine/opengl/shader.rb', line 331

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

#compile_shader(type:) ⇒ Boolean

compile OpenGL Shader of type

Returns:

  • (Boolean)

    whether compilation succeeded

Raises:

  • (ArgumentError)


231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
# File 'lib/cyberarm_engine/opengl/shader.rb', line 231

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.unpack1("L")

  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

  _compiled
end

#compiled?Boolean

Returns whether CyberarmEngine::Shader successfully compiled.

Returns:



320
321
322
# File 'lib/cyberarm_engine/opengl/shader.rb', line 320

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



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

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



261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
# File 'lib/cyberarm_engine/opengl/shader.rb', line 261

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.unpack1("L")

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



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

def preprocess_source(source:)
  lines = source.lines

  lines.each_with_index do |line, i|
    next unless line.start_with?(PREPROCESSOR_CHARACTER)

    preprocessor = line.strip.split(" ")
    lines.delete(line)

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

      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

  lines.join
end

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

whether vertex and fragment files exist on disk

Returns:

  • (Boolean)


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

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

#stopObject

stop using shader, if shader is active



314
315
316
317
# File 'lib/cyberarm_engine/opengl/shader.rb', line 314

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)


353
354
355
356
357
# File 'lib/cyberarm_engine/opengl/shader.rb', line 353

def uniform_boolean(variable, value, location = nil)
  attr_loc = 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)


376
377
378
379
380
# File 'lib/cyberarm_engine/opengl/shader.rb', line 376

def uniform_float(variable, value, location = nil)
  attr_loc = 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)


364
365
366
367
368
# File 'lib/cyberarm_engine/opengl/shader.rb', line 364

def uniform_integer(variable, value, location = nil)
  attr_loc = 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)


341
342
343
344
345
# File 'lib/cyberarm_engine/opengl/shader.rb', line 341

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

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

#uniform_vector3(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)


388
389
390
391
392
# File 'lib/cyberarm_engine/opengl/shader.rb', line 388

def uniform_vector3(variable, value, location = nil)
  attr_loc = location || attribute_location(variable)

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

#uniform_vector4(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)


400
401
402
403
404
# File 'lib/cyberarm_engine/opengl/shader.rb', line 400

def uniform_vector4(variable, value, location = nil)
  attr_loc = location || attribute_location(variable)

  glUniform4f(attr_loc, *value.to_a)
end

#use(&block) ⇒ Object

See Also:



299
300
301
302
303
304
305
306
307
308
309
310
311
# File 'lib/cyberarm_engine/opengl/shader.rb', line 299

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



286
287
288
289
290
291
292
293
294
295
296
# File 'lib/cyberarm_engine/opengl/shader.rb', line 286

def variable(variable)
  loc = glGetUniformLocation(@program, variable)
  if loc == -1
    unless @variable_missing[variable]
      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?"
    end
    @variable_missing[variable] = true
  end
  loc
end