Skip to content

Instantly share code, notes, and snippets.

@vaiorabbit
Created June 9, 2020 14:20
Show Gist options
  • Select an option

  • Save vaiorabbit/b44499fe86aa42e429d82ce312198110 to your computer and use it in GitHub Desktop.

Select an option

Save vaiorabbit/b44499fe86aa42e429d82ce312198110 to your computer and use it in GitHub Desktop.

Revisions

  1. vaiorabbit created this gist Jun 9, 2020.
    279 changes: 279 additions & 0 deletions 06_bump_sdl2_metal.rb
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,279 @@
    # coding: utf-8
    #
    # Ref.: bgfx/examples/06-bump/bump.cpp
    # https://github.com/vaiorabbit/sdl2-bindings
    #
    require 'rmath3d/rmath3d'
    require 'sdl2'
    require_relative '../../bindings/ruby/bgfx.rb'
    require_relative './logo.rb'
    require_relative './utils.rb'

    include SDL2
    include RMath3D

    SDL2.load_lib(SampleUtils.sdl2_dll_path())
    Bgfx.load_lib(SampleUtils.bgfx_dll_path())

    ################################################################################

    class PosNormalTangentTexcoordVertex < FFI::Struct
    @@ms_layout = Bgfx_vertex_layout_t.new

    def self.ms_layout
    @@ms_layout
    end

    layout(
    :m_x, :float,
    :m_y, :float,
    :m_z, :float,
    :m_normal, :uint32,
    :m_tangent, :uint32,
    :m_u, :int16,
    :m_v, :int16
    )

    def self.init()
    Bgfx::bgfx_vertex_layout_begin(@@ms_layout, Bgfx::RendererType::Noop)
    Bgfx::bgfx_vertex_layout_add(@@ms_layout, Bgfx::Attrib::Position, 3, Bgfx::AttribType::Float, false, false)
    Bgfx::bgfx_vertex_layout_add(@@ms_layout, Bgfx::Attrib::Normal, 4, Bgfx::AttribType::Uint8, true, true)
    Bgfx::bgfx_vertex_layout_add(@@ms_layout, Bgfx::Attrib::Tangent, 4, Bgfx::AttribType::Uint8, true, true)
    Bgfx::bgfx_vertex_layout_add(@@ms_layout, Bgfx::Attrib::TexCoord0, 2, Bgfx::AttribType::Int16, true, true)
    Bgfx::bgfx_vertex_layout_end(@@ms_layout)
    end
    end

    # Ref: Array of Structs https://github.com/ffi/ffi/wiki/Structs

    cubeVerticesSrc = [
    [-1.0, 1.0, 1.0, BgfxUtils.encode_normal_rgba8( 0.0, 0.0, 1.0), 0, 0, 0 ],
    [ 1.0, 1.0, 1.0, BgfxUtils.encode_normal_rgba8( 0.0, 0.0, 1.0), 0, 0x7fff, 0 ],
    [-1.0, -1.0, 1.0, BgfxUtils.encode_normal_rgba8( 0.0, 0.0, 1.0), 0, 0, 0x7fff ],
    [ 1.0, -1.0, 1.0, BgfxUtils.encode_normal_rgba8( 0.0, 0.0, 1.0), 0, 0x7fff, 0x7fff ],
    [-1.0, 1.0, -1.0, BgfxUtils.encode_normal_rgba8( 0.0, 0.0, -1.0), 0, 0, 0 ],
    [ 1.0, 1.0, -1.0, BgfxUtils.encode_normal_rgba8( 0.0, 0.0, -1.0), 0, 0x7fff, 0 ],
    [-1.0, -1.0, -1.0, BgfxUtils.encode_normal_rgba8( 0.0, 0.0, -1.0), 0, 0, 0x7fff ],
    [ 1.0, -1.0, -1.0, BgfxUtils.encode_normal_rgba8( 0.0, 0.0, -1.0), 0, 0x7fff, 0x7fff ],
    [-1.0, 1.0, 1.0, BgfxUtils.encode_normal_rgba8( 0.0, 1.0, 0.0), 0, 0, 0 ],
    [ 1.0, 1.0, 1.0, BgfxUtils.encode_normal_rgba8( 0.0, 1.0, 0.0), 0, 0x7fff, 0 ],
    [-1.0, 1.0, -1.0, BgfxUtils.encode_normal_rgba8( 0.0, 1.0, 0.0), 0, 0, 0x7fff ],
    [ 1.0, 1.0, -1.0, BgfxUtils.encode_normal_rgba8( 0.0, 1.0, 0.0), 0, 0x7fff, 0x7fff ],
    [-1.0, -1.0, 1.0, BgfxUtils.encode_normal_rgba8( 0.0, -1.0, 0.0), 0, 0, 0 ],
    [ 1.0, -1.0, 1.0, BgfxUtils.encode_normal_rgba8( 0.0, -1.0, 0.0), 0, 0x7fff, 0 ],
    [-1.0, -1.0, -1.0, BgfxUtils.encode_normal_rgba8( 0.0, -1.0, 0.0), 0, 0, 0x7fff ],
    [ 1.0, -1.0, -1.0, BgfxUtils.encode_normal_rgba8( 0.0, -1.0, 0.0), 0, 0x7fff, 0x7fff ],
    [ 1.0, -1.0, 1.0, BgfxUtils.encode_normal_rgba8( 1.0, 0.0, 0.0), 0, 0, 0 ],
    [ 1.0, 1.0, 1.0, BgfxUtils.encode_normal_rgba8( 1.0, 0.0, 0.0), 0, 0x7fff, 0 ],
    [ 1.0, -1.0, -1.0, BgfxUtils.encode_normal_rgba8( 1.0, 0.0, 0.0), 0, 0, 0x7fff ],
    [ 1.0, 1.0, -1.0, BgfxUtils.encode_normal_rgba8( 1.0, 0.0, 0.0), 0, 0x7fff, 0x7fff ],
    [-1.0, -1.0, 1.0, BgfxUtils.encode_normal_rgba8(-1.0, 0.0, 0.0), 0, 0, 0 ],
    [-1.0, 1.0, 1.0, BgfxUtils.encode_normal_rgba8(-1.0, 0.0, 0.0), 0, 0x7fff, 0 ],
    [-1.0, -1.0, -1.0, BgfxUtils.encode_normal_rgba8(-1.0, 0.0, 0.0), 0, 0, 0x7fff ],
    [-1.0, 1.0, -1.0, BgfxUtils.encode_normal_rgba8(-1.0, 0.0, 0.0), 0, 0x7fff, 0x7fff ],
    ]

    $s_cubeVertices = FFI::MemoryPointer.new(PosNormalTangentTexcoordVertex, cubeVerticesSrc.length)
    cubeVertices = cubeVerticesSrc.length.times.collect do |i|
    PosNormalTangentTexcoordVertex.new($s_cubeVertices + i * PosNormalTangentTexcoordVertex.size)
    end

    cubeVertices.each_with_index do |c, i|
    c[:m_x], c[:m_y], c[:m_z], c[:m_normal], c[:m_tangent], c[:m_u], c[:m_v] = *cubeVerticesSrc[i]
    end


    cubeIndicesSrc = [
    0, 2, 1,
    1, 2, 3,
    4, 5, 6,
    5, 7, 6,

    8, 10, 9,
    9, 10, 11,
    12, 13, 14,
    13, 15, 14,

    16, 18, 17,
    17, 18, 19,
    20, 21, 22,
    21, 23, 22,
    ]
    $s_cubeIndices = FFI::MemoryPointer.new(:uint16, cubeIndicesSrc.length).write_array_of_ushort(cubeIndicesSrc)


    ################################################################################

    $m_vbh = nil # Bgfx_dynamic_vertex_buffer_handle_t
    $m_ibh = nil # Bgfx_dynamic_index_buffer_handle_t
    $m_program = nil # Bgfx_shader_handle_t
    $s_texColor = nil # Bgfx_uniform_handle_t
    $s_texNormal = nil # Bgfx_uniform_handle_t
    $u_lightPosRadius = nil # Bgfx_uniform_handle_t
    $u_lightRgbInnerR = nil # Bgfx_uniform_handle_t
    $m_numLights = 4 # uint16
    $m_textureColor = nil # Bgfx_texture_handle_t
    $m_textureNormal = nil # Bgfx_texture_handle_t

    if __FILE__ == $0
    success = SDL_Init(SDL_INIT_VIDEO)
    exit if success < 0

    SDL_SetHint(SDL_HINT_RENDER_DRIVER, "metal")

    window_width = 1280
    window_height = 720
    window = SDL_CreateWindow("ruby-bgfx : 06-bump", 0, 0, window_width, window_height, 0)

    renderer = SDL_CreateRenderer(window, -1, 0)
    if renderer == nil
    pp "Failed to initialize SDL"
    exit
    end

    nwh = FFI::Pointer.new(:pointer, SampleUtils.native_window_handle(window))
    pd = Bgfx_platform_data_t.new
    pd[:ndt] = nil
    pd[:nwh] = nwh
    pd[:context] = nil
    pd[:backBuffer] = nil
    pd[:backBufferDS] = nil
    Bgfx::bgfx_set_platform_data(pd)

    init = Bgfx_init_t.new
    init[:type] = Bgfx::RendererType::Metal
    init[:vendorId] = Bgfx::Pci_Id_None
    init[:resolution][:width] = window_width
    init[:resolution][:height] = window_height
    init[:resolution][:reset] = Bgfx::Reset_Vsync
    init[:limits][:maxEncoders] = 1
    init[:limits][:transientVbSize] = 6<<20
    init[:limits][:transientIbSize] = 2<<20
    bgfx_init_success = Bgfx::bgfx_init(init)
    pp "Failed to initialize Bgfx" unless bgfx_init_success

    #Bgfx::bgfx_set_debug(Bgfx::Debug_None)
    #Bgfx::bgfx_set_debug(Bgfx::Debug_Wireframe)
    Bgfx::bgfx_set_debug(Bgfx::Debug_Stats)
    Bgfx::bgfx_set_view_clear(0, Bgfx::Clear_Color|Bgfx::Clear_Depth, 0x303080ff, 1.0, 0)

    PosNormalTangentTexcoordVertex.init()

    BgfxUtils.calc_tangents($s_cubeVertices, $s_cubeVertices.size / $s_cubeVertices.type_size, PosNormalTangentTexcoordVertex.ms_layout, cubeIndicesSrc)

    $m_vbh = Bgfx::bgfx_create_vertex_buffer(
    Bgfx::bgfx_make_ref($s_cubeVertices, $s_cubeVertices.size),
    PosNormalTangentTexcoordVertex.ms_layout,
    Bgfx::Buffer_None
    )

    $m_ibh = Bgfx::bgfx_create_index_buffer(
    Bgfx::bgfx_make_ref($s_cubeIndices, $s_cubeIndices.size),
    Bgfx::Buffer_None
    )

    $s_texColor = Bgfx::bgfx_create_uniform("s_texColor", Bgfx::UniformType::Sampler, -1)
    $s_texNormal = Bgfx::bgfx_create_uniform("s_texNormal", Bgfx::UniformType::Sampler, -1)
    $u_lightPosRadius = Bgfx::bgfx_create_uniform("u_lightPosRadius", Bgfx::UniformType::Vec4, $m_numLights)
    $u_lightRgbInnerR = Bgfx::bgfx_create_uniform("u_lightRgbInnerR", Bgfx::UniformType::Vec4, $m_numLights)
    $m_textureColor = BgfxUtils.load_texture("textures/fieldstone-rgba.dds")
    $m_textureNormal = BgfxUtils.load_texture("textures/fieldstone-n.dds")

    $m_program = BgfxUtils.load_program("vs_bump", "fs_bump")

    eye = RVec3.new(0.0, 0.0, -7.0)
    at = RVec3.new(0.0, 0.0, 0.0)
    up = RVec3.new(0.0, 1.0, 0.0)
    mtxLookAt = RMtx4.new.lookAtRH( eye, at, up )
    view = FFI::MemoryPointer.new(:float, 16).write_array_of_float(mtxLookAt.to_a)

    mtxProj = RMtx4.new.perspectiveFovRH( 60.0*Math::PI/180.0, window_width.to_f/window_height.to_f, 0.1, 100.0 ) # TODO bgfx::getCaps()->homogeneousDepth
    proj = FFI::MemoryPointer.new(:float, 16).write_array_of_float(mtxProj.to_a)

    time_prev = SDL_GetTicks()
    time_crnt = time_prev
    time = 0.0

    event = SDL_Event.new
    done = false
    while not done
    while SDL_PollEvent(event) != 0
    # 'type' and 'timestamp' are common members for all SDL Event structs.
    event_type = event[:common][:type]
    event_timestamp = event[:common][:timestamp]

    case event_type
    when SDL_KEYDOWN
    if event[:key][:keysym][:sym] == SDL2::SDLK_ESCAPE
    done = true
    end
    end
    end

    Bgfx::bgfx_reset(window_width, window_height, Bgfx::Reset_None, Bgfx::TextureFormat::Count)

    time_crnt = SDL_GetTicks()
    time += (time_crnt-time_prev).to_f * 0.001
    time_prev = time_crnt

    Bgfx::bgfx_set_view_transform(0, view, proj)
    Bgfx::bgfx_set_view_rect(0, 0, 0, window_width, window_height)
    Bgfx::bgfx_touch(0)

    light_pos_radius = Array.new(4) { Array.new(4, 0.0) }
    $m_numLights.times do |ii|
    light_pos_radius[ii][0] = Math.sin( (time*(0.1 + ii*0.17) + ii*(0.5 * Math::PI) * 1.37 ) )*3.0
    light_pos_radius[ii][1] = Math.cos( (time*(0.2 + ii*0.29) + ii*(0.5 * Math::PI) * 1.49 ) )*3.0
    light_pos_radius[ii][2] = -2.5
    light_pos_radius[ii][3] = 3.0
    end

    Bgfx::bgfx_set_uniform($u_lightPosRadius, light_pos_radius.flatten!.pack("F16"), $m_numLights)

    light_rgb_inner_r = [
    [ 1.0, 0.7, 0.2, 0.8 ],
    [ 0.7, 0.2, 1.0, 0.8 ],
    [ 0.2, 1.0, 0.7, 0.8 ],
    [ 1.0, 0.4, 0.2, 0.8 ],
    ]

    Bgfx::bgfx_set_uniform($u_lightRgbInnerR, light_rgb_inner_r.flatten!.pack("F16"), $m_numLights)

    state = 0 | Bgfx::State_Write_Rgb | Bgfx::State_Write_A | Bgfx::State_Write_Z | Bgfx::State_Depth_Test_Less | Bgfx::State_Msaa

    3.times do |yy|
    3.times do |xx|
    mtxTransform = RMtx4.new.translation(-3.0 + xx * 3.0, -3.0 + yy * 3.0, 0.0) * RMtx4.new.rotationY(time * 0.03 + yy * 0.37) * RMtx4.new.rotationX(time * 0.23 + xx * 0.21)
    mtx = FFI::MemoryPointer.new(:float, 16).write_array_of_float(mtxTransform.to_a)
    Bgfx::bgfx_set_transform(mtx, 1)
    Bgfx::bgfx_set_vertex_buffer(0, $m_vbh, 0, 0xffffffff) # 0xffffffff == UINT32_MAX
    Bgfx::bgfx_set_index_buffer($m_ibh, 0, 0xffffffff) # 0xffffffff == UINT32_MAX
    Bgfx::bgfx_set_texture(0, $s_texColor, $m_textureColor, 0xffffffff) # 0xffffffff == UINT32_MAX
    Bgfx::bgfx_set_texture(1, $s_texNormal, $m_textureNormal, 0xffffffff) # 0xffffffff == UINT32_MAX
    Bgfx::bgfx_set_state(state, 0)

    Bgfx::bgfx_submit(0, $m_program, 0, Bgfx::Discard_All)
    end
    end

    Bgfx::bgfx_frame(false)
    end

    if bgfx_init_success
    Bgfx::bgfx_destroy_uniform($s_texColor)
    Bgfx::bgfx_destroy_uniform($s_texNormal)
    Bgfx::bgfx_destroy_uniform($u_lightPosRadius)
    Bgfx::bgfx_destroy_uniform($u_lightRgbInnerR)
    Bgfx::bgfx_destroy_texture($m_textureNormal)
    Bgfx::bgfx_destroy_texture($m_textureColor)

    Bgfx::bgfx_destroy_program($m_program)
    Bgfx::bgfx_destroy_vertex_buffer($m_vbh)
    Bgfx::bgfx_destroy_index_buffer($m_ibh)

    Bgfx::bgfx_shutdown()
    end

    SDL_DestroyWindow(window)
    SDL_Quit()
    end
    228 changes: 228 additions & 0 deletions utils.rb
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,228 @@
    require 'ffi'
    require 'sdl2'
    require 'rmath3d/rmath3d'

    module BgfxUtils

    ################################################################################

    def self.load_shader(name, runtime_path = "../runtime/")

    shader_path = case Bgfx::bgfx_get_renderer_type()
    when Bgfx::RendererType::OpenGL
    "shaders/glsl/"
    when Bgfx::RendererType::Metal
    "shaders/metal/"
    else
    $stderr.puts "You should not be here!"
    end

    file_path = runtime_path + shader_path + name + ".bin"

    shader_binary = IO.binread(file_path)
    shader_mem = FFI::MemoryPointer.from_string(shader_binary)
    handle = Bgfx::bgfx_create_shader(Bgfx::bgfx_make_ref(shader_mem, shader_mem.size))
    Bgfx::bgfx_set_shader_name(handle, name, 0x7fffffff) # 0x7fffffff == INT32_MAX

    return handle
    end

    def self.load_program(vs_name, fs_name, runtime_path = "../runtime/")
    vsh = load_shader(vs_name, runtime_path)
    fsh = fs_name != nil ? load_shader(fs_name, runtime_path) : Bgfx::Bgfx_Invalid_Handle
    return Bgfx::bgfx_create_program(vsh, fsh, true)
    end

    ################################################################################

    def self.load_texture(name, runtime_path = "../runtime/")
    texture_raw = IO.binread(runtime_path + name)
    texture_mem = FFI::MemoryPointer.from_string(texture_raw)

    return Bgfx::bgfx_create_texture(
    Bgfx::bgfx_make_ref(texture_mem, texture_mem.size),
    Bgfx::Texture_None|Bgfx::Sampler_None,
    0,
    nil
    )
    end

    ################################################################################

    def self.to_unorm(_value, _scale)
    ((_value.clamp(0.0, 1.0) * _scale) + 0.5).floor
    end

    # usage) pp BgfxUtils.encode_normal_rgba8(0.0, 1.0, 0.0).to_s(16) => "8080ff80"
    def self.encode_normal_rgba8(_x, _y = 0.0, _z = 0.0, _w = 0.0)
    to_unorm = lambda { |_value, _scale| ((_value.clamp(0.0, 1.0) * _scale) + 0.5).floor }
    src = [
    _x * 0.5 + 0.5,
    _y * 0.5 + 0.5,
    _z * 0.5 + 0.5,
    _w * 0.5 + 0.5,
    ]
    dst = [
    to_unorm(src[0], 255.0),
    to_unorm(src[1], 255.0),
    to_unorm(src[2], 255.0),
    to_unorm(src[3], 255.0),
    ]
    return dst.pack("C4").unpack1("L")
    end

    ################################################################################

    class PosTexcoord
    def initialize
    @m_pos = FFI::MemoryPointer.new(:float, 4)
    @m_texcoord = FFI::MemoryPointer.new(:float, 4)
    end
    def m_x; @m_pos[0]; end
    def m_y; @m_pos[1]; end
    def m_z; @m_pos[2]; end
    def m_u; @m_texcoord[0]; end
    def m_v; @m_texcoord[1]; end
    end

    def self.calc_tangents(_vertices, _numVertices, _layout, _indices)

    tangents = Array.new (2 * _numVertices) { RVec3.new }
    v0 = PosTexcoord.new
    v1 = PosTexcoord.new
    v2 = PosTexcoord.new

    num = _indices.length / 3
    num.times do |ii|
    indices = _indices[ii * 3, 3]
    i0 = indices[0]
    i1 = indices[1]
    i2 = indices[2]

    Bgfx::bgfx_vertex_unpack(v0.m_x, Bgfx::Attrib::Position, _layout, _vertices, i0)
    Bgfx::bgfx_vertex_unpack(v0.m_u, Bgfx::Attrib::TexCoord0, _layout, _vertices, i0)

    Bgfx::bgfx_vertex_unpack(v1.m_x, Bgfx::Attrib::Position, _layout, _vertices, i1)
    Bgfx::bgfx_vertex_unpack(v1.m_u, Bgfx::Attrib::TexCoord0, _layout, _vertices, i1)

    Bgfx::bgfx_vertex_unpack(v2.m_x, Bgfx::Attrib::Position, _layout, _vertices, i2)
    Bgfx::bgfx_vertex_unpack(v2.m_u, Bgfx::Attrib::TexCoord0, _layout, _vertices, i2)

    bax = v1.m_x.read_float - v0.m_x.read_float
    bay = v1.m_y.read_float - v0.m_y.read_float
    baz = v1.m_z.read_float - v0.m_z.read_float
    bau = v1.m_u.read_float - v0.m_u.read_float
    bav = v1.m_v.read_float - v0.m_v.read_float

    cax = v2.m_x.read_float - v0.m_x.read_float
    cay = v2.m_y.read_float - v0.m_y.read_float
    caz = v2.m_z.read_float - v0.m_z.read_float
    cau = v2.m_u.read_float - v0.m_u.read_float
    cav = v2.m_v.read_float - v0.m_v.read_float

    det = (bau * cav - bav * cau)
    invDet = 1.0 / det

    tx = (bax * cav - cax * bav) * invDet
    ty = (bay * cav - cay * bav) * invDet
    tz = (baz * cav - caz * bav) * invDet

    bx = (cax * bau - bax * cau) * invDet
    by = (cay * bau - bay * cau) * invDet
    bz = (caz * bau - baz * cau) * invDet

    3.times do |jj|
    tanu = tangents[indices[jj] + 0]
    tanv = tangents[indices[jj] + 1]
    tanu[0] += tx
    tanu[1] += ty
    tanu[2] += tz
    tanv[0] += bx
    tanv[1] += by
    tanv[2] += bz
    end
    end

    nxyzw = FFI::MemoryPointer.new(:float, 4)
    tangent = FFI::MemoryPointer.new(:float, 4)
    _numVertices.times do |ii|
    tanu = tangents[ii + 0]
    tanv = tangents[ii + 1]

    Bgfx::bgfx_vertex_unpack(nxyzw, Bgfx::Attrib::Normal, _layout, _vertices, ii)

    normal = RVec3.new(*nxyzw.read_array_of_float(3))
    ndt = RVec3.dot(normal, tanu)
    nxt = RVec3.cross(normal, tanu);
    tmp = (tanu - (ndt * normal)).normalize!

    tangent.write_array_of_float([*tmp.to_a, RVec3.dot(nxt, tanv) < 0 ? -1.0 : 1.0])
    Bgfx::bgfx_vertex_pack(tangent, true, Bgfx::Attrib::Tangent, _layout, _vertices, ii)
    end
    end

    end

    ####################################################################################################

    module SampleUtils

    ################################################################################

    def self.sdl2_dll_path
    path = case RUBY_PLATFORM
    when /mswin|msys|mingw|cygwin/
    Dir.pwd + '/' + 'SDL2.dll'
    when /darwin/
    '/usr/local/lib/libSDL2.dylib'
    when /linux/
    './libSDL2.so'
    else
    raise RuntimeError, "Unknown OS: #{RUBY_PLATFORM}"
    end
    return path
    end

    ################################################################################

    def self.bgfx_dll_path
    path = case RUBY_PLATFORM
    when /mswin|msys|mingw|cygwin/
    Dir.pwd + '/bgfx-shared-libDebug.dll'
    when /darwin/
    './libbgfx-shared-libDebug.dylib'
    when /linux/
    './libbgfx-shared-libDebug.so'
    else
    raise RuntimeError, "Unknown OS: #{RUBY_PLATFORM}"
    end
    return path
    end

    ################################################################################

    def self.native_window_handle(sdl2_window)
    nwh = case RUBY_PLATFORM
    when /mswin|msys|mingw|cygwin/
    wminfo = SDL_SysWMinfo_win.new # TODO Windows
    if SDL_GetWindowWMInfo(sdl2_window, wminfo) == SDL_TRUE
    wminfo[:info][:win][:window]
    else
    nil
    end
    when /darwin/
    wminfo = SDL_SysWMinfo_cocoa.new # TODO Windows
    if SDL_GetWindowWMInfo(sdl2_window, wminfo) == SDL_TRUE
    wminfo[:info][:cocoa][:window]
    else
    nil
    end
    when /linux/
    raise RuntimeError, "Not supported yet: #{RUBY_PLATFORM}"
    else
    raise RuntimeError, "Unknown OS: #{RUBY_PLATFORM}"
    end
    return nwh
    end

    end