Class: CyberarmEngine::Shader

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

Overview

Constant Summary collapse

PREPROCESSOR_CHARACTER =
"@"
@@shaders =
{}

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.



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
88
89
90
91
# File 'lib/cyberarm_engine/shader.rb', line 58

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

  # Only add shader if it successfully compiles

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

Instance Attribute Details

#nameObject (readonly)

Returns the value of attribute name.



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

def name
  @name
end

#programObject (readonly)

Returns the value of attribute program.



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

def program
  @program
end

Class Method Details

.active_shaderObject



29
30
31
# File 'lib/cyberarm_engine/shader.rb', line 29

def self.active_shader
  @active_shader
end

.active_shader=(instance) ⇒ Object



33
34
35
# File 'lib/cyberarm_engine/shader.rb', line 33

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

.add(name, instance) ⇒ Object



8
9
10
# File 'lib/cyberarm_engine/shader.rb', line 8

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

.attribute_location(variable) ⇒ Object

Raises:

  • (RuntimeError)


47
48
49
50
# File 'lib/cyberarm_engine/shader.rb', line 47

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:

  • (Boolean)


21
22
23
# File 'lib/cyberarm_engine/shader.rb', line 21

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

.get(name) ⇒ Object



25
26
27
# File 'lib/cyberarm_engine/shader.rb', line 25

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

.set_uniform(variable, value) ⇒ Object

Raises:

  • (RuntimeError)


52
53
54
55
# File 'lib/cyberarm_engine/shader.rb', line 52

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

.stopObject



37
38
39
40
41
42
43
44
45
# File 'lib/cyberarm_engine/shader.rb', line 37

def self.stop
  shader = Shader.active_shader

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

.use(name, &block) ⇒ Object



12
13
14
15
16
17
18
19
# File 'lib/cyberarm_engine/shader.rb', line 12

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



222
223
224
# File 'lib/cyberarm_engine/shader.rb', line 222

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

#compile_shader(type:) ⇒ Object

Raises:

  • (ArgumentError)


144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/cyberarm_engine/shader.rb', line 144

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:

  • (Boolean)


218
219
220
# File 'lib/cyberarm_engine/shader.rb', line 218

def compiled?
  @compiled
end

#create_shader(type:, source:) ⇒ Object



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/cyberarm_engine/shader.rb', line 97

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

  case type
  when :vertex
    _shader = glCreateShader(GL_VERTEX_SHADER)
  when :fragment
    _shader = glCreateShader(GL_FRAGMENT_SHADER)
  else
    warn "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


169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/cyberarm_engine/shader.rb', line 169

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



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

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

Returns:

  • (Boolean)


93
94
95
# File 'lib/cyberarm_engine/shader.rb', line 93

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

#stopObject



213
214
215
216
# File 'lib/cyberarm_engine/shader.rb', line 213

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

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



232
233
234
235
236
# File 'lib/cyberarm_engine/shader.rb', line 232

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



244
245
246
247
248
# File 'lib/cyberarm_engine/shader.rb', line 244

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



238
239
240
241
242
# File 'lib/cyberarm_engine/shader.rb', line 238

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



226
227
228
229
230
# File 'lib/cyberarm_engine/shader.rb', line 226

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



250
251
252
253
254
# File 'lib/cyberarm_engine/shader.rb', line 250

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



256
257
258
259
260
# File 'lib/cyberarm_engine/shader.rb', line 256

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

  glUniform4f(attr_loc, *value.to_a)
end

#use(&block) ⇒ Object



200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/cyberarm_engine/shader.rb', line 200

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

Returns the location of a uniform variable



191
192
193
194
195
196
197
198
# File 'lib/cyberarm_engine/shader.rb', line 191

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