github.com/cybriq/giocore@v0.0.7-0.20210703034601-cfb9cb5f3900/gpu/internal/convertshaders/hlsl.go (about)

     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  }