読者です 読者をやめる 読者になる 読者になる

WHITELEAF:Kindle応援サイト

KindleでWEB小説を読もう! Narou.rb 公開中

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 {}