lib/cyberarm_engine/shader.rb in cyberarm_engine-0.12.1 vs lib/cyberarm_engine/shader.rb in cyberarm_engine-0.13.0

- old
+ new

@@ -1,205 +1,262 @@ -module CyberarmEngine - # Ref: https://github.com/vaiorabbit/ruby-opengl/blob/master/sample/OrangeBook/brick.rb - class Shader - include OpenGL - @@shaders = {} - - def self.add(name, instance) - @@shaders[name] = instance - end - - def self.use(name, &block) - shader = @@shaders.dig(name) - if shader - shader.use(&block) - else - raise ArgumentError, "Shader '#{name}' not found!" - end - end - - def self.available?(name) - @@shaders.dig(name).is_a?(Shader) - end - - def self.get(name) - @@shaders.dig(name) - end - - def self.active_shader - @active_shader - end - - def self.active_shader=(instance) - @active_shader = instance - end - - def self.stop - shader = Shader.active_shader - - if shader - shader.stop - else - raise ArgumentError, "No active shader to stop!" - end - end - - def self.attribute_location(variable) - raise RuntimeError, "No active shader!" unless Shader.active_shader - Shader.active_shader.attribute_location(variable) - end - - def self.set_uniform(variable, value) - raise RuntimeError, "No active shader!" unless Shader.active_shader - Shader.active_shader.set_uniform(variable, value) - end - - attr_reader :name, :program - def initialize(name:, vertex: "shaders/default.vert", fragment:) - @name = name - @vertex_file = vertex - @fragment_file = fragment - @compiled = false - - @program = nil - - @error_buffer_size = 1024 - @variable_missing = {} - - raise ArgumentError, "Shader files not found: #{@vertex_file} or #{@fragment_file}" unless shader_files_exist? - - create_shaders - compile_shaders - - # Only add shader if it successfully compiles - if @compiled - Shader.add(@name, self) - else - puts "FAILED to compile shader: #{@name}", "" - end - end - - def shader_files_exist? - File.exist?(@vertex_file) && File.exist?(@fragment_file) - end - - def create_shaders - @vertex = glCreateShader(GL_VERTEX_SHADER) - @fragment = glCreateShader(GL_FRAGMENT_SHADER) - - source = [File.read(@vertex_file)].pack('p') - size = [File.size(@vertex_file)].pack('I') - glShaderSource(@vertex, 1, source, size) - - source = [File.read(@fragment_file)].pack('p') - size = [File.size(@fragment_file)].pack('I') - glShaderSource(@fragment, 1, source, size) - end - - def compile_shaders - return unless shader_files_exist? - - glCompileShader(@vertex) - buffer = ' ' - glGetShaderiv(@vertex, GL_COMPILE_STATUS, buffer) - compiled = buffer.unpack('L')[0] - - if compiled == 0 - log = ' ' * @error_buffer_size - glGetShaderInfoLog(@vertex, @error_buffer_size, nil, log) - puts "Shader Error: Program \"#{@name}\"" - puts " Vectex 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 - return - end - - glCompileShader(@fragment) - buffer = ' ' - glGetShaderiv(@fragment, GL_COMPILE_STATUS, buffer) - compiled = buffer.unpack('L')[0] - - if compiled == 0 - log = ' ' * @error_buffer_size - glGetShaderInfoLog(@fragment, @error_buffer_size, nil, log) - puts "Shader Error: Program \"#{@name}\"" - puts " Fragment Shader InfoLog:", " #{log.strip.split("\n").join("\n ")}\n\n" - puts " Shader Compiled status: #{compiled}" - puts " NOTE: assignment of uniforms in shader is illegal!" - puts - return - end - - @program = glCreateProgram - glAttachShader(@program, @vertex) - glAttachShader(@program, @fragment) - 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 - - # Returns the location of a uniform variable - 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 - - 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 - - def stop - Shader.active_shader = nil if Shader.active_shader == self - glUseProgram(0) - end - - def compiled? - @compiled - end - - def attribute_location(variable) - glGetUniformLocation(@program, variable) - end - - def set_uniform(variable, value, location = nil) - attr_loc = location ? location : attribute_location(variable) - - case value.class.to_s.downcase.to_sym - when :integer - glUniform1i(attr_loc, value) - when :float - glUniform1f(attr_loc, value) - when :string - when :array - else - raise NotImplementedError, "Shader support for #{value.class.inspect} not implemented." - end - - Window.handle_gl_error - end - end -end \ No newline at end of file +module CyberarmEngine + # Ref: https://github.com/vaiorabbit/ruby-opengl/blob/master/sample/OrangeBook/brick.rb + class Shader + include OpenGL + @@shaders = {} + PREPROCESSOR_CHARACTER = "@" + + def self.add(name, instance) + @@shaders[name] = instance + end + + def self.use(name, &block) + shader = @@shaders.dig(name) + if shader + shader.use(&block) + else + raise ArgumentError, "Shader '#{name}' not found!" + end + end + + def self.available?(name) + @@shaders.dig(name).is_a?(Shader) + end + + def self.get(name) + @@shaders.dig(name) + end + + def self.active_shader + @active_shader + end + + def self.active_shader=(instance) + @active_shader = instance + end + + def self.stop + shader = Shader.active_shader + + if shader + shader.stop + else + raise ArgumentError, "No active shader to stop!" + end + end + + def self.attribute_location(variable) + raise RuntimeError, "No active shader!" unless Shader.active_shader + Shader.active_shader.attribute_location(variable) + end + + def self.set_uniform(variable, value) + raise RuntimeError, "No active shader!" unless Shader.active_shader + Shader.active_shader.set_uniform(variable, value) + end + + attr_reader :name, :program + 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 + + def shader_files_exist?(vertex:, fragment:) + File.exist?(vertex) && File.exist?(fragment) + end + + 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 + + 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 + + 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 + + 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 + + # Returns the location of a uniform variable + 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 + + 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 + + def stop + Shader.active_shader = nil if Shader.active_shader == self + glUseProgram(0) + end + + def compiled? + @compiled + end + + def attribute_location(variable) + glGetUniformLocation(@program, variable) + end + + 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 + + def uniform_boolean(variable, value, location = nil) + attr_loc = location ? location : attribute_location(variable) + + glUniform1i(attr_loc, value ? 1 : 0) + end + + def uniform_integer(variable, value, location = nil) + attr_loc = location ? location : attribute_location(variable) + + glUniform1i(attr_loc, value) + end + + def uniform_float(variable, value, location = nil) + attr_loc = location ? location : attribute_location(variable) + + glUniform1f(attr_loc, value) + end + + def uniform_vec3(variable, value, location = nil) + attr_loc = location ? location : attribute_location(variable) + + glUniform3f(attr_loc, *value.to_a[0..2]) + end + + def uniform_vec4(variable, value, location = nil) + attr_loc = location ? location : attribute_location(variable) + + glUniform4f(attr_loc, *value.to_a) + end + end +end