lib/mittsu/opengl/program.rb
require 'erb'
require 'mittsu/opengl/shader'
module Mittsu
class OpenGL::Program
attr_reader :id, :program, :uniforms
attr_accessor :code, :used_times, :attributes, :vertex_shader, :fragment_shader
def initialize(renderer, code, material, parameters)
@id = (@@id ||= 1).tap { @@id += 1 }
@renderer = renderer
compile_and_link_program(material, parameters)
cache_uniform_locations(material.shader[:uniforms] || {}, parameters)
cache_attribute_locations(material.attributes || {}, parameters)
@code = code
@used_times = 2
end
private
def compile_and_link_program(material, parameters)
@program = GL.CreateProgram
# 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
compile_shaders(material, parameters)
# 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
# GL.BindAttributeLocation(program, 0, index0_attribute_name)
# end
GL.LinkProgram(@program)
check_for_link_errors
post_link_clean_up
end
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 link_status
ptr = ' '*8
GL.GetProgramiv @program, GL::LINK_STATUS, ptr
ptr.unpack('L')[0]
end
def program_info_log
ptr = ' '*8
GL.GetProgramiv @program, GL::INFO_LOG_LENGTH, ptr
length = ptr.unpack('L')[0]
if length > 0
log = ' '*length
GL.GetProgramInfoLog @program, length, ptr, log
log.unpack("A#{length}")[0]
else
''
end
end
def check_for_link_errors
log_info = program_info_log
if !link_status
puts "ERROR: Mittsu::OpenGL::Program: shader error: #{GL.GetError}, GL::INVALID_STATUS, #{GL.GetProgramParameter(program, GL::VALIDATE_STATUS)}, GL.GetProgramParameterInfoLog, #{log_info}"
end
if !log_info.empty?
puts "WARNING: Mittsu::OpenGL::Program: GL.GetProgramInfoLog, #{log_info}"
# TODO: useless in OpenGL ???
# puts "WARNING: #{GL.GetExtension( 'OPENGL_debug_shaders' ).getTranslatedShaderSource( GL.VertexShader )}"
# puts "WARNING: #{GL.GetExtension( 'OPENGL_debug_shaders' ).getTranslatedShaderSource( GL.FragmentShader )}"
end
end
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
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
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
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
def compile_shaders(material, parameters)
shadow_map_type_define = get_shadow_map_define(parameters[:shadow_map_type])
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)
gamma_factor_define = (@renderer.gamma_factor > 0) ? @renderer.gamma_factor : 1.0
custom_defines = generate_defines(material.defines || {})
if false # material.is_a?(RawShaderMaterial) # TODO: when RawShaderMaterial exists
prefix_vertex = ''
prefix_fragment = ''
else
prefix_vertex = File.read(File.expand_path('../shader/templates/vertex.glsl.erb', __FILE__))
prefix_fragment = File.read(File.expand_path('../shader/templates/fragment.glsl.erb', __FILE__))
end
@vertex_shader = OpenGL::Shader.new(GL::VERTEX_SHADER, compile_shader_template(prefix_vertex + material.shader[:vertex_shader], binding))
@fragment_shader = OpenGL::Shader.new(GL::FRAGMENT_SHADER, compile_shader_template(prefix_fragment + material.shader[:fragment_shader], binding))
GL.AttachShader(@program, @vertex_shader.shader)
GL.AttachShader(@program, @fragment_shader.shader)
end
def compile_shader_template(template, b)
ERB.new(template).result(b)
end
def post_link_clean_up
GL.DeleteShader(@vertex_shader.shader)
GL.DeleteShader(@fragment_shader.shader)
end
def cache_uniform_locations(uniforms, parameters)
identifiers = [
'viewMatrix',
'modelViewMatrix',
'projectionMatrix',
'normalMatrix',
'modelMatrix',
'cameraPosition',
'morphTargetInfluences',
'bindMatrix',
'bindMatrixInverse'
]
if parameters[:use_vertex_texture]
identifiers << 'boneTexture'
identifiers << 'boneTextureWidth'
identifiers << 'boneTextureHeight'
else
identifiers << 'boneGlobalMatrices'
end
if parameters[:logarithmic_depth_buffer]
identifiers << 'logDepthBufFC'
end
uniforms.each do |k, v|
identifiers << k.to_s
end
@uniforms = {}
identifiers.each do |id|
@uniforms[id] = GL.GetUniformLocation(program, id)
end
end
def cache_attribute_locations(attributes, parameters)
identifiers = [
'position',
'normal',
'uv',
'uv2',
'tangent',
'color',
'skinIndex',
'skinWeight',
'lineDistance'
]
parameters[:max_morph_targets].times do |i|
identifiers << "morphTarget#{i}"
end
parameters[:max_morph_normals].times do |i|
identifiers << "morphNormal#{i}"
end
attributes.each do |k, v|
identifiers << k
end
@attributes = {}
identifiers.each do |id|
@attributes[id] = GL.GetAttribLocation(program, id)
end
end
end
end