tools: add vshader (#12195)
parent
bff7cc5f20
commit
678e3a210d
|
@ -48,13 +48,14 @@ const (
|
|||
|
||||
// Snooped from cmd/v/v.v, vlib/v/pref/pref.v
|
||||
const (
|
||||
auto_complete_commands = [
|
||||
auto_complete_commands = [
|
||||
// simple_cmd
|
||||
'fmt',
|
||||
'up',
|
||||
'vet',
|
||||
'self',
|
||||
'tracev',
|
||||
'shader',
|
||||
'symlink',
|
||||
'bin2v',
|
||||
'test',
|
||||
|
@ -90,7 +91,7 @@ const (
|
|||
'build',
|
||||
'build-module',
|
||||
]
|
||||
auto_complete_flags = [
|
||||
auto_complete_flags = [
|
||||
'-apk',
|
||||
'-show-timings',
|
||||
'-check-syntax',
|
||||
|
@ -149,7 +150,7 @@ const (
|
|||
'-version',
|
||||
'--version',
|
||||
]
|
||||
auto_complete_flags_doc = [
|
||||
auto_complete_flags_doc = [
|
||||
'-all',
|
||||
'-f',
|
||||
'-h',
|
||||
|
@ -167,7 +168,7 @@ const (
|
|||
'-s',
|
||||
'-l',
|
||||
]
|
||||
auto_complete_flags_fmt = [
|
||||
auto_complete_flags_fmt = [
|
||||
'-c',
|
||||
'-diff',
|
||||
'-l',
|
||||
|
@ -175,7 +176,7 @@ const (
|
|||
'-debug',
|
||||
'-verify',
|
||||
]
|
||||
auto_complete_flags_bin2v = [
|
||||
auto_complete_flags_bin2v = [
|
||||
'-h',
|
||||
'--help',
|
||||
'-m',
|
||||
|
@ -185,10 +186,22 @@ const (
|
|||
'-w',
|
||||
'--write',
|
||||
]
|
||||
auto_complete_flags_self = [
|
||||
auto_complete_flags_shader = [
|
||||
'help',
|
||||
'h',
|
||||
'force-update',
|
||||
'u',
|
||||
'verbose',
|
||||
'v',
|
||||
'slang',
|
||||
'l',
|
||||
'output',
|
||||
'o',
|
||||
]
|
||||
auto_complete_flags_self = [
|
||||
'-prod',
|
||||
]
|
||||
auto_complete_compilers = [
|
||||
auto_complete_compilers = [
|
||||
'cc',
|
||||
'gcc',
|
||||
'tcc',
|
||||
|
@ -368,6 +381,9 @@ fn auto_complete_request(args []string) []string {
|
|||
'self' { // 'v self -<tab>' -> flags.
|
||||
list = get_flags(auto_complete_flags_self, part)
|
||||
}
|
||||
'shader' { // 'v shader -<tab>' -> flags.
|
||||
list = get_flags(auto_complete_flags_shader, part)
|
||||
}
|
||||
else {
|
||||
for flag in auto_complete_flags {
|
||||
if flag == part {
|
||||
|
|
|
@ -0,0 +1,299 @@
|
|||
// Copyright (c) 2021 Lars Pontoppidan. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
//
|
||||
// vshader aids in generating special shader code C headers via sokol-shdc's 'annotated GLSL' format to any
|
||||
// supported target formats that sokol_gfx supports internally.
|
||||
//
|
||||
// vshader bootstraps itself by downloading it's own dependencies to a system cache directory on first run.
|
||||
//
|
||||
// Please see https://github.com/floooh/sokol-tools/blob/master/docs/sokol-shdc.md#feature-overview
|
||||
// for a more in-depth overview of the specific tool in use.
|
||||
//
|
||||
// The shader language used is, as described on the overview page linked above, an 'annotated GLSL'
|
||||
// and 'modern GLSL' (v450) shader language format.
|
||||
import os
|
||||
import io.util
|
||||
import flag
|
||||
import net.http
|
||||
|
||||
const (
|
||||
tool_name = os.file_name(os.executable())
|
||||
tool_version = '0.0.1'
|
||||
tool_description = "Compile shaders in sokol's annotated GLSL format to C headers for use with sokol based apps"
|
||||
cache_dir = os.join_path(os.cache_dir(), 'v', tool_name)
|
||||
runtime_os = os.user_os()
|
||||
)
|
||||
|
||||
const (
|
||||
supported_hosts = ['linux', 'macos', 'windows']
|
||||
supported_slangs = [
|
||||
'glsl330', // desktop GL
|
||||
'glsl100', // GLES2 / WebGL
|
||||
'glsl300es', // GLES3 / WebGL2
|
||||
'hlsl4', // D3D11
|
||||
'hlsl5', // D3D11
|
||||
'metal_macos', // Metal on macOS
|
||||
'metal_ios', // Metal on iOS device
|
||||
'metal_sim', // Metal on iOS simulator
|
||||
'wgpu', // WebGPU
|
||||
]
|
||||
default_slangs = [
|
||||
'glsl330',
|
||||
'glsl100',
|
||||
'glsl300es',
|
||||
// 'hlsl4', and hlsl5 can't be used at the same time
|
||||
'hlsl5',
|
||||
'metal_macos',
|
||||
'metal_ios',
|
||||
'metal_sim',
|
||||
'wgpu',
|
||||
]
|
||||
|
||||
shdc_version = '33d2e4cc'
|
||||
shdc_urls = {
|
||||
'windows': 'https://github.com/floooh/sokol-tools-bin/raw/33d2e4cc26088c6c28eaef5467990f8940d15aab/bin/win32/sokol-shdc.exe'
|
||||
'macos': 'https://github.com/floooh/sokol-tools-bin/raw/33d2e4cc26088c6c28eaef5467990f8940d15aab/bin/osx/sokol-shdc'
|
||||
'linux': 'https://github.com/floooh/sokol-tools-bin/raw/33d2e4cc26088c6c28eaef5467990f8940d15aab/bin/linux/sokol-shdc'
|
||||
}
|
||||
shdc_version_file = os.join_path(cache_dir, 'sokol-shdc.version')
|
||||
shdc = shdc_exe()
|
||||
shdc_exe_name = 'sokol-shdc.exe'
|
||||
)
|
||||
|
||||
struct Options {
|
||||
show_help bool
|
||||
verbose bool
|
||||
force_update bool
|
||||
slangs []string
|
||||
}
|
||||
|
||||
struct CompileOptions {
|
||||
verbose bool
|
||||
slangs []string
|
||||
invoke_path string
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if os.args.len == 1 {
|
||||
println('Usage: $tool_name PATH \n$tool_description\n$tool_name -h for more help...')
|
||||
exit(1)
|
||||
}
|
||||
mut fp := flag.new_flag_parser(os.args[1..])
|
||||
fp.application(tool_name)
|
||||
fp.version(tool_version)
|
||||
fp.description(tool_description)
|
||||
fp.arguments_description('PATH [PATH]...')
|
||||
fp.skip_executable()
|
||||
// Collect tool options
|
||||
opt := Options{
|
||||
show_help: fp.bool('help', `h`, false, 'Show this help text.')
|
||||
force_update: fp.bool('force-update', `u`, false, 'Force update of the sokol-shdc tool.')
|
||||
verbose: fp.bool('verbose', `v`, false, 'Be verbose about the tools progress.')
|
||||
slangs: fp.string_multi('slang', `l`, 'Shader dialects to generate code for. Default is all. Available: $supported_slangs')
|
||||
}
|
||||
if opt.show_help {
|
||||
println(fp.usage())
|
||||
exit(0)
|
||||
}
|
||||
|
||||
ensure_external_tools(opt) or { panic(err) }
|
||||
|
||||
input_paths := fp.finalize() or { panic(err) }
|
||||
|
||||
for path in input_paths {
|
||||
if os.exists(path) {
|
||||
compile_shaders(opt, path) or { panic(err) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// shader_program_name returns the name of the program from `shader_file`.
|
||||
// shader_program_name returns a blank string if no @program entry could be found.
|
||||
fn shader_program_name(shader_file string) string {
|
||||
shader_program := os.read_lines(shader_file) or { return '' }
|
||||
for line in shader_program {
|
||||
if line.contains('@program ') {
|
||||
return line.all_after('@program ').all_before(' ')
|
||||
}
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
// validate_shader_file returns an error if `shader_file` isn't valid.
|
||||
fn validate_shader_file(shader_file string) ? {
|
||||
shader_program := os.read_lines(shader_file) or {
|
||||
return error('shader program at "$shader_file" could not be opened for reading')
|
||||
}
|
||||
mut has_program_directive := false
|
||||
for line in shader_program {
|
||||
if line.contains('@program ') {
|
||||
has_program_directive = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !has_program_directive {
|
||||
return error('shader program at "$shader_file" is missing a "@program" directive.')
|
||||
}
|
||||
}
|
||||
|
||||
// compile_shaders compiles all `*.glsl` files found in `input_path`
|
||||
// to their C header file representatives.
|
||||
fn compile_shaders(opt Options, input_path string) ? {
|
||||
mut path := os.real_path(input_path)
|
||||
path = path.trim_right('/')
|
||||
if os.is_file(path) {
|
||||
path = os.dir(path)
|
||||
}
|
||||
|
||||
mut shader_files := []string{}
|
||||
collect(path, mut shader_files)
|
||||
|
||||
if shader_files.len == 0 {
|
||||
if opt.verbose {
|
||||
eprintln('$tool_name found no shader files to compile for "$path"')
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
for shader_file in shader_files {
|
||||
// It could be the user has WIP shader files lying around not used,
|
||||
// so we just report that there's something wrong
|
||||
validate_shader_file(shader_file) or {
|
||||
eprintln(err)
|
||||
continue
|
||||
}
|
||||
co := CompileOptions{
|
||||
verbose: opt.verbose
|
||||
slangs: opt.slangs
|
||||
invoke_path: path
|
||||
}
|
||||
// Currently sokol-shdc allows for multiple --input flags
|
||||
// - but it's only the last entry that's actually compiled/used
|
||||
// Given this fact - we can only compile one '.glsl' file to one C '.h' header
|
||||
compile_shader(co, shader_file) ?
|
||||
}
|
||||
}
|
||||
|
||||
// compile_shader compiles `shader_file` to a C header file.
|
||||
fn compile_shader(opt CompileOptions, shader_file string) ? {
|
||||
path := opt.invoke_path
|
||||
// The output convetion, for now, is to use the name of the .glsl file
|
||||
mut out_file := os.file_name(shader_file).all_before_last('.') + '.h'
|
||||
out_file = os.join_path(path, out_file)
|
||||
|
||||
mut slangs := opt.slangs.clone()
|
||||
if opt.slangs.len == 0 {
|
||||
slangs = default_slangs.clone()
|
||||
}
|
||||
|
||||
header_name := os.file_name(out_file)
|
||||
if opt.verbose {
|
||||
eprintln('$tool_name generating shader code for $slangs in header "$header_name" in "$path" from $shader_file')
|
||||
}
|
||||
|
||||
cmd := '$shdc --input "$shader_file" --output "$out_file" --slang "' + slangs.join(':') + '"'
|
||||
if opt.verbose {
|
||||
eprintln('$tool_name executing:\n$cmd')
|
||||
}
|
||||
res := os.execute(cmd)
|
||||
if res.exit_code != 0 {
|
||||
eprintln('$tool_name failed generating shader includes:\n $res.output\n $cmd')
|
||||
exit(1)
|
||||
}
|
||||
if opt.verbose {
|
||||
program_name := shader_program_name(shader_file)
|
||||
eprintln('$tool_name usage example in V:\n\nimport sokol.gfx\n\n#include "$header_name"\n\nfn C.${program_name}_shader_desc(gfx.Backend) &C.sg_shader_desc\n')
|
||||
}
|
||||
}
|
||||
|
||||
// collect recursively collects `.glsl` file entries from `path` in `list`.
|
||||
fn collect(path string, mut list []string) {
|
||||
if !os.is_dir(path) {
|
||||
return
|
||||
}
|
||||
mut files := os.ls(path) or { return }
|
||||
for file in files {
|
||||
p := os.join_path(path, file)
|
||||
if os.is_dir(p) && !os.is_link(p) {
|
||||
collect(p, mut list)
|
||||
} else if os.exists(p) {
|
||||
if os.file_ext(p) == '.glsl' {
|
||||
list << os.real_path(p)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ensure_external_tools returns nothing if the external
|
||||
// tools can be setup or is already in place.
|
||||
fn ensure_external_tools(opt Options) ? {
|
||||
if !os.exists(cache_dir) {
|
||||
os.mkdir_all(cache_dir) ?
|
||||
}
|
||||
if opt.force_update {
|
||||
download_shdc(opt) ?
|
||||
return
|
||||
}
|
||||
|
||||
is_shdc_available := os.is_file(shdc)
|
||||
is_shdc_executable := os.is_executable(shdc)
|
||||
if is_shdc_available && is_shdc_executable {
|
||||
if opt.verbose {
|
||||
version := os.read_file(shdc_version_file) or { 'unknown' }
|
||||
eprintln('$tool_name using sokol-shdc version $version at "$shdc"')
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
download_shdc(opt) ?
|
||||
}
|
||||
|
||||
// shdc_exe returns an absolute path to the `sokol-shdc` tool.
|
||||
// Please note that the tool isn't guaranteed to actually be present, nor is
|
||||
// it guaranteed that it can be invoked.
|
||||
fn shdc_exe() string {
|
||||
return os.join_path(cache_dir, shdc_exe_name)
|
||||
}
|
||||
|
||||
// download_shdc downloads the `sokol-shdc` tool to an OS specific cache directory.
|
||||
fn download_shdc(opt Options) ? {
|
||||
// We want to use the same, runtime, OS type as this tool is invoked on.
|
||||
download_url := shdc_urls[runtime_os] or { '' }
|
||||
if download_url == '' {
|
||||
return error('$tool_name failed to download an external dependency "sokol-shdc" for ${runtime_os}.\nThe supported host platforms for shader compilation is $supported_hosts')
|
||||
}
|
||||
update_to_shdc_version := os.read_file(shdc_version_file) or { shdc_version }
|
||||
file := shdc_exe()
|
||||
if opt.verbose {
|
||||
if shdc_version != update_to_shdc_version && os.exists(file) {
|
||||
eprintln('$tool_name updating sokol-shdc to version $update_to_shdc_version ...')
|
||||
} else {
|
||||
eprintln('$tool_name installing sokol-shdc version $update_to_shdc_version ...')
|
||||
}
|
||||
}
|
||||
if os.exists(file) {
|
||||
os.rm(file) ?
|
||||
}
|
||||
|
||||
mut dtmp_file, dtmp_path := util.temp_file(util.TempFileOptions{ path: os.dir(file) }) ?
|
||||
dtmp_file.close()
|
||||
if opt.verbose {
|
||||
eprintln('$tool_name downloading sokol-shdc from $download_url')
|
||||
}
|
||||
http.download_file(download_url, dtmp_path) or {
|
||||
os.rm(dtmp_path) ?
|
||||
return error('$tool_name failed to download sokol-shdc needed for shader compiling: $err')
|
||||
}
|
||||
// Make it executable
|
||||
os.chmod(dtmp_path, 0o775) ?
|
||||
// Move downloaded file in place
|
||||
os.mv(dtmp_path, file) ?
|
||||
if runtime_os in ['linux', 'macos'] {
|
||||
// Use the .exe file ending to minimize platform friction.
|
||||
os.mv(file, shdc) ?
|
||||
}
|
||||
// Update internal version file
|
||||
os.write_file(shdc_version_file, update_to_shdc_version) ?
|
||||
}
|
Loading…
Reference in New Issue