class CyberarmEngine::Shader

Ref: github.com/vaiorabbit/ruby-opengl/blob/master/sample/OrangeBook/brick.rb

Constants

PREPROCESSOR_CHARACTER

Attributes

active_shader[RW]
name[R]
program[R]

Public Class Methods

add(name, instance) click to toggle source

add instance of {Shader} to cache

@param name [String] @param instance [Shader]

# File lib/cyberarm_engine/opengl/shader.rb, line 12
def self.add(name, instance)
  @@shaders[name] = instance
end
attribute_location(variable) click to toggle source

returns location of OpenGL Shader uniform

@param variable [String]

# 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) click to toggle source

returns whether {Shader} with name is in cache

@param name [String] @return [Boolean]

# File lib/cyberarm_engine/opengl/shader.rb, line 54
def self.available?(name)
  @@shaders.dig(name).is_a?(Shader)
end
delete(name) click to toggle source

removes {Shader} from cache and cleans up

@param name [String]

# 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) click to toggle source

returns instance of {Shader}, if it exists

@param name [String] @return [Shader?]

# File lib/cyberarm_engine/opengl/shader.rb, line 62
def self.get(name)
  @@shaders.dig(name)
end
new(name:, fragment:, includes_dir: nil, vertex: "shaders/default.vert") click to toggle source
# 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
set_uniform(variable, value) click to toggle source

sets variable to value

@param variable [String] @param value

# 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
stop() click to toggle source

stops using currently active {Shader}

# 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) click to toggle source

runs block using {Shader} with name

@example

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

@param name [String] name of {Shader} to use @return [void]

# 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

Public Instance Methods

attribute_location(variable) click to toggle source

returns location of a uniform variable

@note Use {#variable} for friendly error handling @see variable Shader#variable

@param variable [String] @return [Integer]

# File lib/cyberarm_engine/opengl/shader.rb, line 331
def attribute_location(variable)
  glGetUniformLocation(@program, variable)
end
compile_shader(type:) click to toggle source

compile OpenGL Shader of type

@return [Boolean] whether compilation succeeded

# 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?() click to toggle source

@return [Boolean] whether {Shader} successfully compiled

# File lib/cyberarm_engine/opengl/shader.rb, line 320
def compiled?
  @compiled
end
create_shader(type:, source:) click to toggle source

creates an OpenGL Shader of type using source

@param type [Symbol] valid values are: :vertex, :fragment @param source [String] source code for shader

# 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
preprocess_source(source:) click to toggle source

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

@example

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

@param source shader source code

# 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:) click to toggle source

whether vertex and fragment files exist on disk

@return [Boolean]

# File lib/cyberarm_engine/opengl/shader.rb, line 153
def shader_files_exist?(vertex:, fragment:)
  File.exist?(vertex) && File.exist?(fragment)
end
stop() click to toggle source

stop using shader, if shader is active

# 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) click to toggle source

send Boolean to {Shader}

@param variable [String] @param value [Boolean] @param location [Integer] @return [void]

# 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) click to toggle source

send Float to {Shader}

@param variable [String] @param value [Float] @param location [Integer] @return [void]

# 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) click to toggle source

send Integer to {Shader} @param variable [String] @param value [Integer] @param location [Integer] @return [void]

# 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) click to toggle source

send {Transform} to {Shader}

@param variable [String] @param value [Transform] @param location [Integer] @return [void]

# 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_vec3(variable, value, location = nil) click to toggle source

send {Vector} (x, y, z) to {Shader}

@param variable [String] @param value [Vector] @param location [Integer] @return [void]

# File lib/cyberarm_engine/opengl/shader.rb, line 388
def uniform_vec3(variable, value, location = nil)
  attr_loc = location || attribute_location(variable)

  glUniform3f(attr_loc, *value.to_a[0..2])
end
uniform_vec4(variable, value, location = nil) click to toggle source

send {Vector} to {Shader}

@param variable [String] @param value [Vector] @param location [Integer] @return [void]

# File lib/cyberarm_engine/opengl/shader.rb, line 400
def uniform_vec4(variable, value, location = nil)
  attr_loc = location || attribute_location(variable)

  glUniform4f(attr_loc, *value.to_a)
end
use(&block) click to toggle source

@see Shader.use Shader.use

# 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) click to toggle source

Returns the location of a uniform variable

@param variable [String] @return [Integer] location of uniform

# 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