DXRuby::Shader 使うときに HLSL から変数名を取得してみる
DXRuby で Shader::Core オブジェクトを生成するとき、
core = Shader::Core.new(hlsl,{:g_size => :float})
のようにHLSL内のグローバル変数名をわざわざ教えてやらないでも、HLSLのソースコードを見ればいいんじゃないかな〜と前から思ってたので書いてみた。
shader = DXRubyUtil::Shader.new(hlsl) puts shader["Flash"] # => #<DXRuby::Shader:0xfeb440> puts shader[] # => #<DXRuby::Shader:0xfeb440>
こんな風に書けるが、Shader自体を継承したい場合はまた違う形に変形が必要か。HLSLの仕様をあまり理解してないのでうまくパースできてないかもしれない。
以下にコードを載せます。
require "dxruby" require "strscan" module DXRubyUtil class Shader def initialize(hlsl) variables = Parser.get_variables(hlsl) @core = ::Shader::Core.new(hlsl, get_variable_types(variables)) @techniques = get_techniques(variables) @shader = {} end def [](technique = nil) unless technique technique = @techniques.first or raise Parser::TechniqueNotFound end return @shader[technique] ||= ::Shader.new(@core, technique) end protected VARIABLE_TYPE_PATTERN = /^(#{%w(int float texture)*"|"})/ def get_variable_types(variables) types = {} variables.each do |name, type| next if name == "tex0" if VARIABLE_TYPE_PATTERN=~ type types[name.intern] = $1.intern end end return types end def get_techniques(variables) techniques = [] variables.each do |name, type| if type == "technique" techniques << name end end return techniques end class Parser; end end end class DXRubyUtil::Shader::Parser class TechniqueNotFound < StandardError def initialize super("technique not found") end end def initialize(src) @src = src end def self.get_variables(src) return self.new(src).parse end def parse @ss = StringScanner.new(@src) delete_braces delete_bracket delete_comments return extract_variables end def delete_braces(depth = 0) result = "" unless depth == 0 if @ss.skip(/[^}]*{/) delete_braces(depth + 1) end @ss.skip_until(/}/) return end until @ss.eos? if @ss.skip_until(/{/) result << @ss.pre_match + "{};" delete_braces(depth + 1) @ss.string = @ss.rest else result << @ss.rest break end end @ss.string = result end def delete_bracket @ss.string = @ss.string.gsub(/\[.*?\]/m, "[]") end def delete_comments @ss.string = @ss.string.gsub(/\/\*.*?\*\//m, "").gsub(/\/\/.*$/, "") end def extract_variables expressions = @ss.string.split(";").map { |s| delete_prefix(s).strip }.select { |s| s != "" } scanner = StringScanner.new("") variables = {} exist_technique = false expressions.each { |exp| scanner.string = exp next unless scanner.scan(/^(\w+)\s+/) type = scanner[1] if type == "technique" unless scanner.scan(/\w+/) raise TechniqueNotFound end variables[scanner[0]] = "technique" exist_technique = true next else next if /{}$/ =~ exp end scanner.rest.split(",").map { |s| variables[s.split("=")[0].strip] = type } } unless exist_technique raise TechniqueNotFound end return variables end PREFIXES = /(#{%w(extern nointerpolation shared static uniform volatile const raw_major column_major)*"|"})/ def delete_prefix(str) return str.gsub(PREFIXES, "") end end shader = DXRubyUtil::Shader.new(DATA.read) puts shader["Flash"] puts shader[] __END__ float3 g_color; float g_level; texture tex0; technique Flash {}