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.



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

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.



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

def active_shader
  @active_shader
end

Instance Attribute Details

#nameObject (readonly)

Returns the value of attribute name.



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

def name
  @name
end

#programObject (readonly)

Returns the value of attribute program.



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

def program
  @program
end

Class Method Details

.add(name, instance) ⇒ Object

add instance of CyberarmEngine::Shader to cache

Parameters:

  • name (String)
  • instance (Shader)


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

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

.attribute_location(variable) ⇒ Object

returns location of OpenGL Shader uniform

Parameters:

  • variable (String)


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

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)


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

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)


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

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:



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

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

.set_uniform(variable, value) ⇒ Object

sets variable to value

Parameters:

  • variable (String)
  • value


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

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



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

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:



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

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:



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

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

#compile_shader(type:) ⇒ Boolean

compile OpenGL Shader of type

Returns:

  • (Boolean)

    whether compilation succeeded

Raises:

  • (ArgumentError)


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

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:



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

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



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

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



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

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



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

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)


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

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

#stopObject

stop using shader, if shader is active



316
317
318
319
# File 'lib/cyberarm_engine/opengl/shader.rb', line 316

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)


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

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)


378
379
380
381
382
# File 'lib/cyberarm_engine/opengl/shader.rb', line 378

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)


366
367
368
369
370
# File 'lib/cyberarm_engine/opengl/shader.rb', line 366

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)


343
344
345
346
347
# File 'lib/cyberarm_engine/opengl/shader.rb', line 343

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)


390
391
392
393
394
# File 'lib/cyberarm_engine/opengl/shader.rb', line 390

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)


402
403
404
405
406
# File 'lib/cyberarm_engine/opengl/shader.rb', line 402

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:



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

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



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

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