lib/mittsu/renderers/opengl/opengl_program.rb in mittsu-0.1.1 vs lib/mittsu/renderers/opengl/opengl_program.rb in mittsu-0.1.2

- old
+ new

@@ -1,286 +1,192 @@ +require 'erb' + require 'mittsu/renderers/opengl/opengl_shader' module Mittsu class OpenGLProgram attr_reader :id, :program, :uniforms, :attributes attr_accessor :code, :used_times, :attributes, :vertex_shader, :fragment_shader def initialize(renderer, code, material, parameters) - defines = material.defines || {} # TODO: setting to default object... ? - uniforms = material[:_opengl_shader][:uniforms] - attributes = material.attributes || [] # TODO: setting to default array... ? + @id = (@@id ||= 1).tap { @@id += 1 } + @renderer = renderer - vertex_shader = material[:_opengl_shader][:vertex_shader] - fragment_shader = material[:_opengl_shader][:fragment_shader] + compile_and_link_program(material, parameters) + material_impl = material.implementation(@renderer) + cache_uniform_locations(material_impl.shader[:uniforms] || {}, parameters) + cache_attribute_locations(material.attributes || {}, parameters) + + @code = code + @used_times = 2 + end + + private + + def compile_and_link_program(material, parameters) + @program = glCreateProgram + # TODO: necessary for OpenGL? # index0_attribute_name = material.index0_attribute_name # # if index0_attribute_name.nil? && parameters[:morph_targets] # # programs with morph_targets displace position of attribute 0 # # index0_attribute_name = 'position' # end - shadow_map_type_define = 'SHADOWMAP_TYPE_BASIC' + compile_shaders(material, parameters) - if parameters[:shadow_map_type] == PCFShadowMap - shadow_map_type_define = 'SHADOWMAP_TYPE_PCF_SOFT' - elsif parameters[:shadow_map_type] == PCFSoftShadowMap - shadow_map_type_define = 'SHADOWMAP_TYPE_PCF_SOFT' - end + # if !index0_attribute_name.nil? + # TODO: is this necessary in OpenGL ??? + # Force a particular attribute to index 0. + # because potentially expensive emulation is done by __browser__ if attribute 0 is disabled. (no browser here!) + # And, color, for example is often automatically bound to index 0 so disabling it - env_map_type_define = 'ENVMAP_TYPE_CUBE' - env_map_mode_define = 'ENVMAP_MODE_REFLECTION' - env_map_blending_define = 'ENVMAP_BLENDING_MULTIPLY' + # glBindAttributeLocation(program, 0, index0_attribute_name) + # end - if parameters[:env_map] - case material.env_map.mapping - when CubeReflectionMapping, CubeRefractionMapping - env_map_type_define = 'ENVMAP_TYPE_CUBE' - when EquirectangularReflectionMapping, EquirectangularRefractionMapping - env_map_type_define = 'ENVMAP_TYPE_EQUIREC' - when SphericalReflectionMapping - env_map_type_define = 'ENVMAP_TYPE_SPHERE' - end + glLinkProgram(@program) + check_for_link_errors + post_link_clean_up + end - case material.env_map.mapping - when CubeRefractionMapping, EquirectangularRefractionMapping - env_map_mode_define 'ENVMAP_MODE_REFRACTION' - end + def generate_defines(defines) + chunks = [] - case material.combine - when MultiplyOperation - env_map_blending_define = 'ENVMAP_BLENDING_MULTIPLY' - when MixOperation - env_map_blending_define = 'ENVMAP_BLENDING_MIX' - when AddOperation - env_map_blending_define = 'ENVMAP_BLENDING_ADD' - end + defines.each do |d, value| + next if value == false + + chunk = "#define #{d} #{value}" + chunks << chunk end - gamma_factor_define = (renderer.gamma_factor > 0) ? renderer.gamma_factor : 1.0 + chunks.join("\n") + end - # puts 'building new program' + def link_status + ptr = ' '*8 + glGetProgramiv @program, GL_LINK_STATUS, ptr + ptr.unpack('L')[0] + end - # + def program_info_log + ptr = ' '*8 + glGetProgramiv @program, GL_INFO_LOG_LENGTH, ptr + length = ptr.unpack('L')[0] - custom_defines = generate_defines(defines) - - # - - @program = glCreateProgram - - if false # material.is_a?(RawShaderMaterial) # TODO: when RawShaderMaterial exists - prefix_vertex = '' - prefix_fragment = '' + if length > 0 + log = ' '*length + glGetProgramInfoLog @program, length, ptr, log + log.unpack("A#{length}")[0] else - prefix_vertex = [ - '#version 330', - # TODO: do we need precision for an OpenGL program? - # "precision #{parameters[:precision]} float;", - # "precision #{parameters[:precision]} int;", + '' + end + end - custom_defines, + def check_for_link_errors + log_info = program_info_log - parameters[:supports_vertex_textures] ? '#define VERTEX_TEXTURES' : '', + if !link_status + puts "ERROR: Mittsu::OpenGLProgram: shader error: #{glGetError}, GL_INVALID_STATUS, #{glGetProgramParameter(program, GL_VALIDATE_STATUS)}, glGetProgramParameterInfoLog, #{log_info}" + end - renderer.gamma_input ? '#define GAMMA_INPUT' : '', - renderer.gamma_output ? '#define GAMMA_OUTPUT' : '', - "#define GAMMA_FACTOR #{gamma_factor_define}", + if !log_info.empty? + puts "WARNING: Mittsu::OpenGLProgram: glGetProgramInfoLog, #{log_info}" + # TODO: useless in OpenGL ??? + # puts "WARNING: #{glGetExtension( 'OPENGL_debug_shaders' ).getTranslatedShaderSource( glVertexShader )}" + # puts "WARNING: #{glGetExtension( 'OPENGL_debug_shaders' ).getTranslatedShaderSource( glFragmentShader )}" + end + end - "#define MAX_DIR_LIGHTS #{parameters[:max_dir_lights]}", - "#define MAX_POINT_LIGHTS #{parameters[:max_point_lights]}", - "#define MAX_SPOT_LIGHTS #{parameters[:max_spot_lights]}", - "#define MAX_HEMI_LIGHTS #{parameters[:max_hemi_lights]}", + def get_shadow_map_define(shadow_map_param) + case shadow_map_param + when PCFShadowMap + 'SHADOWMAP_TYPE_PCF' + when PCFSoftShadowMap + 'SHADOWMAP_TYPE_PCF_SOFT' + else + 'SHADOWMAP_TYPE_BASIC' + end + end - "#define MAX_SHADOWS #{parameters[:max_shadows]}", + def get_env_map_type_define(env_map_param, material) + return 'ENVMAP_TYPE_CUBE' unless env_map_param + case material.env_map.mapping + when CubeReflectionMapping, CubeRefractionMapping + 'ENVMAP_TYPE_CUBE' + when EquirectangularReflectionMapping, EquirectangularRefractionMapping + 'ENVMAP_TYPE_EQUIREC' + when SphericalReflectionMapping + 'ENVMAP_TYPE_SPHERE' + else + 'ENVMAP_TYPE_CUBE' + end + end - "#define MAX_BONES #{parameters[:max_bones]}", + def get_env_map_mode_define(env_map_param, material) + return 'ENVMAP_MODE_REFLECTION' unless env_map_param + case material.env_map.mapping + when CubeRefractionMapping, EquirectangularRefractionMapping + 'ENVMAP_MODE_REFRACTION' + else + 'ENVMAP_MODE_REFLECTION' + end + end - parameters[:map] ? '#define USE_MAP' : '', - parameters[:env_map] ? '#define USE_ENVMAP' : '', - parameters[:env_map] ? "#define #{env_map_mode_define}" : '', - parameters[:light_map] ? '#define USE_LIGHTMAP' : '', - parameters[:bump_map] ? '#define USE_BUMPMAP' : '', - parameters[:normal_map] ? '#define USE_NORMALMAP' : '', - parameters[:specular_map] ? '#define USE_SPECULARMAP' : '', - parameters[:alpha_map] ? '#define USE_ALPHAMAP' : '', - parameters[:vertex_colors] ? '#define USE_COLOR' : '', - - parameters[:flat_shading] ? '#define FLAT_SHADED': '', - - parameters[:skinning] ? '#define USE_SKINNING' : '', - parameters[:use_vertex_texture] ? '#define BONE_TEXTURE' : '', - - parameters[:morph_targets] ? '#define USE_MORPHTARGETS' : '', - parameters[:morph_normals] ? '#define USE_MORPHNORMALS' : '', - parameters[:wrap_around] ? '#define WRAP_AROUND' : '', - parameters[:double_sided] ? '#define DOUBLE_SIDED' : '', - parameters[:flip_sided] ? '#define FLIP_SIDED' : '', - - parameters[:shadow_map_enabled] ? '#define USE_SHADOWMAP' : '', - parameters[:shadow_map_enabled] ? "#define #{shadow_map_type_define}" : '', - parameters[:shadow_map_debug] ? '#define SHADOWMAP_DEBUG' : '', - parameters[:shadow_map_cascade] ? '#define SHADOWMAP_CASCADE' : '', - - parameters[:size_attenuation] ? '#define USE_SIZEATTENUATION' : '', - - parameters[:logarithmic_depth_buffer] ? '#define USE_LOGDEPTHBUF' : '', - #renderer._glExtensionFragDepth ? '#define USE_LOGDEPTHBUF_EXT' : '', - - - 'uniform mat4 modelMatrix;', - 'uniform mat4 modelViewMatrix;', - 'uniform mat4 projectionMatrix;', - 'uniform mat4 viewMatrix;', - 'uniform mat3 normalMatrix;', - 'uniform vec3 cameraPosition;', - - 'in vec3 position;', - 'in vec3 normal;', - 'in vec2 uv;', - 'in vec2 uv2;', - - '#ifdef USE_COLOR', - - ' in vec3 color;', - - '#endif', - - '#ifdef USE_MORPHTARGETS', - - ' in vec3 morphTarget0;', - ' in vec3 morphTarget1;', - ' in vec3 morphTarget2;', - ' in vec3 morphTarget3;', - - ' #ifdef USE_MORPHNORMALS', - - ' in vec3 morphNormal0;', - ' in vec3 morphNormal1;', - ' in vec3 morphNormal2;', - ' in vec3 morphNormal3;', - - ' #else', - - ' in vec3 morphTarget4;', - ' in vec3 morphTarget5;', - ' in vec3 morphTarget6;', - ' in vec3 morphTarget7;', - - ' #endif', - - '#endif', - - '#ifdef USE_SKINNING', - - ' in vec4 skinIndex;', - ' in vec4 skinWeight;', - - '#endif', - ].reject(&:empty?).join("\n") + "\n" - - prefix_fragment = [ - '#version 330', - # TODO: do we need precision for an OpenGL program? - # "precison #{parameters[:precision]} float;", - # "precison #{parameters[:precision]} int;", - - # (parameters[:bump_map] || parameters[:normal_map] || parameters[:flat_shading]) ? '#extension GL_OES_standard_derivatives : enable' : '', # TODO: oes extension in OpenGL? - - custom_defines, - - "#define MAX_DIR_LIGHTS #{parameters[:max_dir_lights]}", - "#define MAX_POINT_LIGHTS #{parameters[:max_point_lights]}", - "#define MAX_SPOT_LIGHTS #{parameters[:max_spot_lights]}", - "#define MAX_HEMI_LIGHTS #{parameters[:max_hemi_lights]}", - - "#define MAX_SHADOWS #{parameters[:max_shadows]}", - - parameters[:alpha_test] ? "#define ALPHATEST #{parameters[:alpha_test].to_f}" : '', - - renderer.gamma_input ? '#define GAMMA_INPUT' : '', - renderer.gamma_output ? '#define GAMMA_OUTPUT' : '', - "#define GAMMA_FACTOR #{gamma_factor_define}", - - (parameters[:use_fog] && parameters[:fog]) ? '#define USE_FOG' : '', - (parameters[:use_fog] && parameters[:fog_exp]) ? '#define FOG_EXP2' : '', - - parameters[:map] ? '#define USE_MAP' : '', - parameters[:env_map] ? '#define USE_ENVMAP' : '', - parameters[:env_map] ? "#define #{env_map_type_define}" : '', - parameters[:env_map] ? "#define #{env_map_mode_define}" : '', - parameters[:env_map] ? "#define #{env_map_blending_define}" : '', - parameters[:light_map] ? '#define USE_LIGHTMAP' : '', - parameters[:bump_map] ? '#define USE_BUMPMAP' : '', - parameters[:normal_map] ? '#define USE_NORMALMAP' : '', - parameters[:specular_map] ? '#define USE_SPECULARMAP' : '', - parameters[:alpha_map] ? '#define USE_ALPHAMAP' : '', - parameters[:vertex_colors] ? '#define USE_COLOR' : '', - - parameters[:flat_shading] ? '#define FLAT_SHADED' : '', - - parameters[:metal] ? '#define METAL' : '', - parameters[:wrap_around] ? '#define WRAP_AROUND' : '', - parameters[:double_sided] ? '#define DOUBLE_SIDED' : '', - parameters[:flip_sided] ? '#define FLIP_SIDED' : '', - - - parameters[:shadow_map_enabled] ? '#define USE_SHADOWMAP' : '', - parameters[:shadow_map_enabled] ? "#define #{shadow_map_type_define}" : '', - parameters[:shadow_map_debug] ? '#define SHADOWMAP_DEBUG' : '', - parameters[:'shadow_map_cascade'] ? '#define SHADOWMAP_CASCADE' : '', - - parameters[:logarithmic_depth_buffer] ? '#define USE_LOGDEPTHBUF' : '', - #renderer._glExtensionFragDepth ? '#define USE_LOGDEPTHBUF_EXT' : '', - - 'uniform mat4 viewMatrix;', - 'uniform vec3 cameraPosition;', - - 'layout(location = 0) out vec4 fragColor;' - ].reject(&:empty?).join("\n") + "\n" + def get_env_map_blending_define(env_map_param, material) + return 'ENVMAP_BLENDING_MULTIPLY' unless env_map_param + case material.combine + when MultiplyOperation + 'ENVMAP_BLENDING_MULTIPLY' + when MixOperation + 'ENVMAP_BLENDING_MIX' + when AddOperation + 'ENVMAP_BLENDING_ADD' + else + 'ENVMAP_BLENDING_MULTIPLY' end + end - gl_vertex_shader = OpenGLShader.new(GL_VERTEX_SHADER, prefix_vertex + vertex_shader) - gl_fragment_shader = OpenGLShader.new(GL_FRAGMENT_SHADER, prefix_fragment + fragment_shader) + def compile_shaders(material, parameters) + material_impl = material.implementation(@renderer) + shadow_map_type_define = get_shadow_map_define(parameters[:shadow_map_type]) - glAttachShader(@program, gl_vertex_shader.shader) - glAttachShader(@program, gl_fragment_shader.shader) + env_map_type_define = get_env_map_type_define(parameters[:env_map], material) + env_map_mode_define = get_env_map_mode_define(parameters[:env_map], material) + env_map_blending_define = get_env_map_blending_define(parameters[:env_map], material) - # if !index0_attribute_name.nil? - # TODO: is this necessary in OpenGL ??? - # Force a particular attribute to index 0. - # because potentially expensive emulation is done by __browser__ if attribute 0 is disabled. (no browser here!) - # And, color, for example is often automatically bound to index 0 so disabling it + gamma_factor_define = (@renderer.gamma_factor > 0) ? @renderer.gamma_factor : 1.0 - # glBindAttributeLocation(program, 0, index0_attribute_name) - # end + custom_defines = generate_defines(material.defines || {}) - glLinkProgram(@program) - - log_info = program_info_log - - if !link_status - puts "ERROR: Mittsu::OpenGLProgram: shader error: #{glGetError}, GL_INVALID_STATUS, #{glGetProgramParameter(program, GL_VALIDATE_STATUS)}, glGetProgramParameterInfoLog, #{program_log_info}" + if false # material.is_a?(RawShaderMaterial) # TODO: when RawShaderMaterial exists + prefix_vertex = '' + prefix_fragment = '' + else + prefix_vertex = File.read(File.expand_path('../../shaders/shader_templates/vertex.glsl.erb', __FILE__)) + prefix_fragment = File.read(File.expand_path('../../shaders/shader_templates/fragment.glsl.erb', __FILE__)) end - if !log_info.empty? - puts "WARNING: Mittsu::OpenGLProgram: glGetProgramInfoLog, #{log_info}" - # TODO: useless in OpenGL ??? - # puts "WARNING: #{glGetExtension( 'OPENGL_debug_shaders' ).getTranslatedShaderSource( glVertexShader )}" - # puts "WARNING: #{glGetExtension( 'OPENGL_debug_shaders' ).getTranslatedShaderSource( glFragmentShader )}" - end + @vertex_shader = OpenGLShader.new(GL_VERTEX_SHADER, compile_shader_template(prefix_vertex + material_impl.shader[:vertex_shader], binding)) + @fragment_shader = OpenGLShader.new(GL_FRAGMENT_SHADER, compile_shader_template(prefix_fragment + material_impl.shader[:fragment_shader], binding)) - # clean up + glAttachShader(@program, @vertex_shader.shader) + glAttachShader(@program, @fragment_shader.shader) + end - glDeleteShader(gl_vertex_shader.shader) - glDeleteShader(gl_fragment_shader.shader) + def compile_shader_template(template, b) + ERB.new(template).result(b) + end - # cache uniform locations + def post_link_clean_up + glDeleteShader(@vertex_shader.shader) + glDeleteShader(@fragment_shader.shader) + end + def cache_uniform_locations(uniforms, parameters) identifiers = [ 'viewMatrix', 'modelViewMatrix', 'projectionMatrix', 'normalMatrix', @@ -305,14 +211,17 @@ uniforms.each do |k, v| identifiers << k.to_s end - @uniforms = cache_uniform_locations(program, identifiers) + @uniforms = {} + identifiers.each do |id| + @uniforms[id] = glGetUniformLocation(program, id) + end + end - # cache attributes locations - + def cache_attribute_locations(attributes, parameters) identifiers = [ 'position', 'normal', 'uv', 'uv2', @@ -333,70 +242,12 @@ attributes.each do |k, v| identifiers << k end - @attributes = cache_attribute_locations(program, identifiers) - - @id = (@@id ||= 1).tap { @@id += 1 } - @code = code - @used_times = 2 - @vertex_shader = gl_vertex_shader - @fragment_shader = gl_fragment_shader - end - - private - - def generate_defines(defines) - chunks = [] - - defines.each do |d, value| - next if value == false - - chunk = "#define #{d} #{value}" - chunks << chunk - end - - chunks.join("\n") - end - - def cache_uniform_locations(program, identifiers) - uniforms = {} - + @attributes = {} identifiers.each do |id| - uniforms[id] = glGetUniformLocation(program, id) - end - - uniforms - end - - def cache_attribute_locations(program, identifiers) - attributes = {} - - identifiers.each do |id| - attributes[id] = glGetAttribLocation(program, id) - end - - attributes - end - - def link_status - ptr = ' '*8 - glGetProgramiv @program, GL_LINK_STATUS, ptr - ptr.unpack('L')[0] - end - - def program_info_log - ptr = ' '*8 - glGetProgramiv @program, GL_INFO_LOG_LENGTH, ptr - length = ptr.unpack('L')[0] - - if length > 0 - log = ' '*length - glGetProgramInfoLog @program, length, ptr, log - log.unpack("A#{length}")[0] - else - '' + @attributes[id] = glGetAttribLocation(program, id) end end end end