hlsl.go raw

   1  // SPDX-License-Identifier: Unlicense OR MIT
   2  
   3  package main
   4  
   5  import (
   6  	"bytes"
   7  	"fmt"
   8  	"io"
   9  	"io/ioutil"
  10  	"os/exec"
  11  	"path/filepath"
  12  	"runtime"
  13  	"strings"
  14  )
  15  
  16  // FXC is hlsl compiler that targets ShaderModel 5.x and lower.
  17  type FXC struct {
  18  	Bin     string
  19  	WorkDir WorkDir
  20  }
  21  
  22  func NewFXC() *FXC { return &FXC{Bin: "fxc.exe"} }
  23  
  24  // Compile compiles the input shader.
  25  func (fxc *FXC) Compile(path, variant string, input []byte, entryPoint string, profileVersion string) (string, error) {
  26  	base := fxc.WorkDir.Path(filepath.Base(path), variant, profileVersion)
  27  	pathin := base + ".in"
  28  	pathout := base + ".out"
  29  	result := pathout
  30  
  31  	if err := fxc.WorkDir.WriteFile(pathin, input); err != nil {
  32  		return "", fmt.Errorf("unable to write shader to disk: %w", err)
  33  	}
  34  
  35  	cmd := exec.Command(fxc.Bin)
  36  	if runtime.GOOS != "windows" {
  37  		cmd = exec.Command("wine", fxc.Bin)
  38  		if err := winepath(&pathin, &pathout); err != nil {
  39  			return "", err
  40  		}
  41  	}
  42  
  43  	var profile string
  44  	switch filepath.Ext(path) {
  45  	case ".frag":
  46  		profile = "ps_" + profileVersion
  47  	case ".vert":
  48  		profile = "vs_" + profileVersion
  49  	case ".comp":
  50  		profile = "cs_" + profileVersion
  51  	default:
  52  		return "", fmt.Errorf("unrecognized shader type %s", path)
  53  	}
  54  
  55  	cmd.Args = append(cmd.Args,
  56  		"/Fo", pathout,
  57  		"/T", profile,
  58  		"/E", entryPoint,
  59  		pathin,
  60  	)
  61  
  62  	output, err := cmd.CombinedOutput()
  63  	if err != nil {
  64  		info := ""
  65  		if runtime.GOOS != "windows" {
  66  			info = "If the fxc tool cannot be found, set WINEPATH to the Windows path for the Windows SDK.\n"
  67  		}
  68  		return "", fmt.Errorf("%s\n%sfailed to run %v: %w", output, info, cmd.Args, err)
  69  	}
  70  
  71  	compiled, err := ioutil.ReadFile(result)
  72  	if err != nil {
  73  		return "", fmt.Errorf("unable to read output %q: %w", pathout, err)
  74  	}
  75  
  76  	return string(compiled), nil
  77  }
  78  
  79  // DXC is hlsl compiler that targets ShaderModel 6.0 and newer.
  80  type DXC struct {
  81  	Bin     string
  82  	WorkDir WorkDir
  83  }
  84  
  85  func NewDXC() *DXC { return &DXC{Bin: "dxc"} }
  86  
  87  // Compile compiles the input shader.
  88  func (dxc *DXC) Compile(path, variant string, input []byte, entryPoint string, profile string) (string, error) {
  89  	base := dxc.WorkDir.Path(filepath.Base(path), variant, profile)
  90  	pathin := base + ".in"
  91  	pathout := base + ".out"
  92  	result := pathout
  93  
  94  	if err := dxc.WorkDir.WriteFile(pathin, input); err != nil {
  95  		return "", fmt.Errorf("unable to write shader to disk: %w", err)
  96  	}
  97  
  98  	cmd := exec.Command(dxc.Bin)
  99  
 100  	cmd.Args = append(cmd.Args,
 101  		"-Fo", pathout,
 102  		"-T", profile,
 103  		"-E", entryPoint,
 104  		"-Qstrip_reflect",
 105  		pathin,
 106  	)
 107  
 108  	output, err := cmd.CombinedOutput()
 109  	if err != nil {
 110  		return "", fmt.Errorf("%s\nfailed to run %v: %w", output, cmd.Args, err)
 111  	}
 112  
 113  	compiled, err := ioutil.ReadFile(result)
 114  	if err != nil {
 115  		return "", fmt.Errorf("unable to read output %q: %w", pathout, err)
 116  	}
 117  
 118  	return string(compiled), nil
 119  }
 120  
 121  // winepath uses the winepath tool to convert a paths to Windows format.
 122  // The returned path can be used as arguments for Windows command line tools.
 123  func winepath(paths ...*string) error {
 124  	winepath := exec.Command("winepath", "--windows")
 125  	for _, path := range paths {
 126  		winepath.Args = append(winepath.Args, *path)
 127  	}
 128  	// Use a pipe instead of Output, because winepath may have left wineserver
 129  	// running for several seconds as a grandchild.
 130  	out, err := winepath.StdoutPipe()
 131  	if err != nil {
 132  		return fmt.Errorf("unable to start winepath: %w", err)
 133  	}
 134  	if err := winepath.Start(); err != nil {
 135  		return fmt.Errorf("unable to start winepath: %w", err)
 136  	}
 137  	var buf bytes.Buffer
 138  	if _, err := io.Copy(&buf, out); err != nil {
 139  		return fmt.Errorf("unable to run winepath: %w", err)
 140  	}
 141  	winPaths := strings.Split(strings.TrimSpace(buf.String()), "\n")
 142  	for i, path := range paths {
 143  		*path = winPaths[i]
 144  	}
 145  	return nil
 146  }
 147