From 3ead2f5daf6afd6748d6470da6b36ec19fcfe93d Mon Sep 17 00:00:00 2001 From: Cyberarm Date: Wed, 7 Aug 2019 12:02:22 -0500 Subject: [PATCH] Added Shader handling class, made Text re-render text_shadow if anything affecting shadow is changed. --- lib/cyberarm_engine.rb | 1 + lib/cyberarm_engine/lib/shader.rb | 197 ++++++++++++++++++++++++++++ lib/cyberarm_engine/objects/text.rb | 40 +++++- 3 files changed, 233 insertions(+), 5 deletions(-) create mode 100644 lib/cyberarm_engine/lib/shader.rb diff --git a/lib/cyberarm_engine.rb b/lib/cyberarm_engine.rb index a7f2b0e..89ba8c2 100644 --- a/lib/cyberarm_engine.rb +++ b/lib/cyberarm_engine.rb @@ -9,6 +9,7 @@ require_relative "cyberarm_engine/engine" require_relative "cyberarm_engine/lib/bounding_box" require_relative "cyberarm_engine/lib/vector" +require_relative "cyberarm_engine/lib/shader" if defined?(OpenGL) require_relative "cyberarm_engine/background" require_relative "cyberarm_engine/objects/text" diff --git a/lib/cyberarm_engine/lib/shader.rb b/lib/cyberarm_engine/lib/shader.rb new file mode 100644 index 0000000..85c428d --- /dev/null +++ b/lib/cyberarm_engine/lib/shader.rb @@ -0,0 +1,197 @@ +module CyberarmEngine + # Ref: https://github.com/vaiorabbit/ruby-opengl/blob/master/sample/OrangeBook/brick.rb + class Shader + include OpenGL + + def self.add(name, instance) + @shaders ||= {} + @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.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 diff --git a/lib/cyberarm_engine/objects/text.rb b/lib/cyberarm_engine/objects/text.rb index 5e9cefe..0dbeea9 100644 --- a/lib/cyberarm_engine/objects/text.rb +++ b/lib/cyberarm_engine/objects/text.rb @@ -2,8 +2,8 @@ module CyberarmEngine class Text CACHE = {} - attr_accessor :x, :y, :z, :size, :factor_x, :factor_y, :color, :shadow, :shadow_size, :options - attr_reader :text, :textobject + attr_accessor :x, :y, :z, :size, :options + attr_reader :text, :textobject, :factor_x, :factor_y, :color, :shadow, :shadow_size, :shadow_alpha, :shadow_color def initialize(text, options={}) @text = text.to_s || "" @@ -22,6 +22,8 @@ module CyberarmEngine @shadow = true if options[:shadow] == nil @shadow_size = options[:shadow_size] ? options[:shadow_size] : 1 @shadow_alpha= options[:shadow_alpha] ? options[:shadow_alpha] : 30 + @shadow_alpha= options[:shadow_alpha] ? options[:shadow_alpha] : 30 + @shadow_color= options[:shadow_color] @textobject = check_cache(@size, @font) if @alignment @@ -67,6 +69,35 @@ module CyberarmEngine @text = string end + def factor_x=(n) + @rendered_shadow = nil + @factor_x = n + end + def factor_y=(n) + @rendered_shadow = nil + @factor_y = n + end + def color=(color) + @rendered_shadow = nil + @color = color + end + def shadow=(boolean) + @rendered_shadow = nil + @shadow = boolean + end + def shadow_size=(n) + @rendered_shadow = nil + @shadow_size = n + end + def shadow_alpha=(n) + @rendered_shadow = nil + @shadow_alpha = n + end + def shadow_color=(n) + @rendered_shadow = nil + @shadow_color = n + end + def width textobject.text_width(@text) end @@ -77,9 +108,8 @@ module CyberarmEngine def draw if @shadow && !ARGV.join.include?("--no-shadow") - @shadow_alpha = 30 if @color.alpha > 30 - @shadow_alpha = @color.alpha if @color.alpha <= 30 - shadow_color = Gosu::Color.rgba(@color.red, @color.green, @color.blue, @shadow_alpha) + shadow_alpha = @color.alpha <= 30 ? @color.alpha : @shadow_alpha + shadow_color = @shadow_color ? @shadow_color : Gosu::Color.rgba(@color.red, @color.green, @color.blue, shadow_alpha) _x = @shadow_size _y = @shadow_size