v

Зеркало из https://github.com/vlang/v
Форк
0
/
vshader.v 
304 строки · 9.9 Кб
1
// Copyright (c) 2024 Lars Pontoppidan. All rights reserved.
2
// Use of this source code is governed by an MIT license
3
// that can be found in the LICENSE file.
4
//
5
// vshader aids in generating special shader code C headers via sokol-shdc's 'annotated GLSL' format to any
6
// supported target formats that sokol_gfx supports internally.
7
//
8
// vshader bootstraps itself by downloading it's own dependencies to a system cache directory on first run.
9
//
10
// Please see https://github.com/floooh/sokol-tools/blob/master/docs/sokol-shdc.md#feature-overview
11
// for a more in-depth overview of the specific tool in use.
12
//
13
// The shader language used is, as described on the overview page linked above, an 'annotated GLSL'
14
// and 'modern GLSL' (v450) shader language format.
15
import os
16
import time
17
import io.util
18
import flag
19
import net.http
20

21
const shdc_full_hash = '0d91b038780614a867f2c8eecd7d935d76bcaae3'
22
const tool_version = '0.0.4'
23
const tool_description = "Compile shaders in sokol's annotated GLSL format to C headers for use with sokol based apps"
24
const tool_name = os.file_name(os.executable())
25
const cache_dir = os.join_path(os.cache_dir(), 'v', tool_name)
26
const runtime_os = os.user_os()
27
const supported_hosts = ['linux', 'macos', 'windows']
28
const supported_slangs = [
29
	'glsl430', // default desktop OpenGL backend (SOKOL_GLCORE)
30
	'glsl410', // default macOS desktop OpenGL
31
	'glsl300es', // OpenGLES3 and WebGL2 (SOKOL_GLES3)
32
	'hlsl4', // Direct3D11 with HLSL4 (SOKOL_D3D11)
33
	'hlsl5', // Direct3D11 with HLSL5 (SOKOL_D3D11)
34
	'metal_macos', // Metal on macOS (SOKOL_METAL)
35
	'metal_ios', // Metal on iOS devices (SOKOL_METAL)
36
	'metal_sim', // Metal on iOS simulator (SOKOL_METAL)
37
	'wgsl', // WebGPU (SOKOL_WGPU)
38
	'reflection',
39
]
40
const default_slangs = [
41
	'glsl410',
42
	'glsl300es',
43
	// 'hlsl4', and hlsl5 can't be used at the same time
44
	'hlsl5',
45
	'metal_macos',
46
	'metal_ios',
47
	'metal_sim',
48
	'wgsl',
49
]
50

51
const shdc_version = shdc_full_hash[0..8]
52
const shdc_urls = {
53
	'windows': 'https://github.com/floooh/sokol-tools-bin/raw/${shdc_full_hash}/bin/win32/sokol-shdc.exe'
54
	'macos':   'https://github.com/floooh/sokol-tools-bin/raw/${shdc_full_hash}/bin/osx/sokol-shdc'
55
	'linux':   'https://github.com/floooh/sokol-tools-bin/raw/${shdc_full_hash}/bin/linux/sokol-shdc'
56
	'osx_a64': 'https://github.com/floooh/sokol-tools-bin/raw/${shdc_full_hash}/bin/osx_arm64/sokol-shdc'
57
}
58
const shdc_version_file = os.join_path(cache_dir, 'sokol-shdc.version')
59
const shdc_exe = os.join_path(cache_dir, 'sokol-shdc.exe')
60

61
struct Options {
62
	show_help    bool
63
	verbose      bool
64
	force_update bool
65
	slangs       []string
66
}
67

68
struct CompileOptions {
69
	verbose     bool
70
	slangs      []string
71
	invoke_path string
72
}
73

74
fn main() {
75
	if os.args.len == 1 {
76
		println('Usage: ${tool_name} PATH \n${tool_description}\n${tool_name} -h for more help...')
77
		exit(1)
78
	}
79
	mut fp := flag.new_flag_parser(os.args[1..])
80
	fp.application(tool_name)
81
	fp.version(tool_version)
82
	fp.description(tool_description)
83
	fp.arguments_description('PATH [PATH]...')
84
	fp.skip_executable()
85
	// Collect tool options
86
	opt := Options{
87
		show_help:    fp.bool('help', `h`, false, 'Show this help text.')
88
		force_update: fp.bool('force-update', `u`, false, 'Force update of the sokol-shdc tool.')
89
		verbose:      fp.bool('verbose', `v`, false, 'Be verbose about the tools progress.')
90
		slangs:       fp.string_multi('slang', `l`, 'Shader dialects to generate code for. Default is all.\n                            Available dialects: ${supported_slangs}')
91
	}
92
	if opt.show_help {
93
		println(fp.usage())
94
		exit(0)
95
	}
96

97
	ensure_external_tools(opt) or { panic(err) }
98

99
	input_paths := fp.finalize() or { panic(err) }
100

101
	for path in input_paths {
102
		if os.exists(path) {
103
			compile_shaders(opt, path) or { panic(err) }
104
		}
105
	}
106
}
107

108
// shader_program_name returns the name of the program from `shader_file`.
109
// shader_program_name returns a blank string if no @program entry could be found.
110
fn shader_program_name(shader_file string) string {
111
	shader_program := os.read_lines(shader_file) or { return '' }
112
	for line in shader_program {
113
		if line.contains('@program ') {
114
			return line.all_after('@program ').all_before(' ')
115
		}
116
	}
117
	return ''
118
}
119

120
// validate_shader_file returns an error if `shader_file` isn't valid.
121
fn validate_shader_file(shader_file string) ! {
122
	shader_program := os.read_lines(shader_file) or {
123
		return error('shader program at "${shader_file}" could not be opened for reading')
124
	}
125
	mut has_program_directive := false
126
	for line in shader_program {
127
		if line.contains('@program ') {
128
			has_program_directive = true
129
			break
130
		}
131
	}
132
	if !has_program_directive {
133
		return error('shader program at "${shader_file}" is missing a "@program" directive.')
134
	}
135
}
136

137
// compile_shaders compiles all `*.glsl` files found in `input_path`
138
// to their C header file representatives.
139
fn compile_shaders(opt Options, input_path string) ! {
140
	mut path := os.real_path(input_path)
141
	path = path.trim_right('/')
142
	if os.is_file(path) {
143
		path = os.dir(path)
144
	}
145

146
	mut shader_files := []string{}
147
	collect(path, mut shader_files)
148

149
	if shader_files.len == 0 {
150
		if opt.verbose {
151
			eprintln('${tool_name} found no shader files to compile for "${path}"')
152
		}
153
		return
154
	}
155

156
	for shader_file in shader_files {
157
		// It could be the user has WIP shader files lying around not used,
158
		// so we just report that there's something wrong
159
		validate_shader_file(shader_file) or {
160
			eprintln(err)
161
			continue
162
		}
163
		co := CompileOptions{
164
			verbose:     opt.verbose
165
			slangs:      opt.slangs
166
			invoke_path: path
167
		}
168
		// Currently sokol-shdc allows for multiple --input flags
169
		// - but it's only the last entry that's actually compiled/used
170
		// Given this fact - we can only compile one '.glsl' file to one C '.h' header
171
		compile_shader(co, shader_file)!
172
	}
173
}
174

175
// compile_shader compiles `shader_file` to a C header file.
176
fn compile_shader(opt CompileOptions, shader_file string) ! {
177
	path := opt.invoke_path
178
	// The output convention, for now, is to use the name of the .glsl file
179
	mut out_file := os.file_name(shader_file).all_before_last('.') + '.h'
180
	out_file = os.join_path(path, out_file)
181

182
	mut slangs := opt.slangs.clone()
183
	if opt.slangs.len == 0 {
184
		slangs = default_slangs.clone()
185
	}
186

187
	header_name := os.file_name(out_file)
188
	if opt.verbose {
189
		eprintln('${tool_name} generating shader code for ${slangs} in header "${header_name}" in "${path}" from ${shader_file}')
190
	}
191

192
	cmd :=
193
		'${os.quoted_path(shdc_exe)} --input ${os.quoted_path(shader_file)} --output ${os.quoted_path(out_file)} --slang ' +
194
		os.quoted_path(slangs.join(':'))
195
	if opt.verbose {
196
		eprintln('${tool_name} executing:\n${cmd}')
197
	}
198
	res := os.execute(cmd)
199
	if res.exit_code != 0 {
200
		eprintln('${tool_name} failed generating shader includes:\n        ${res.output}\n        ${cmd}')
201
		exit(1)
202
	}
203
	if opt.verbose {
204
		program_name := shader_program_name(shader_file)
205
		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) &gfx.ShaderDesc\n')
206
	}
207
}
208

209
// collect recursively collects `.glsl` file entries from `path` in `list`.
210
fn collect(path string, mut list []string) {
211
	if !os.is_dir(path) {
212
		return
213
	}
214
	mut files := os.ls(path) or { return }
215
	for file in files {
216
		p := os.join_path(path, file)
217
		if os.is_dir(p) && !os.is_link(p) {
218
			collect(p, mut list)
219
		} else if os.exists(p) {
220
			if os.file_ext(p) == '.glsl' {
221
				list << os.real_path(p)
222
			}
223
		}
224
	}
225
	return
226
}
227

228
// ensure_external_tools returns nothing if the external
229
// tools can be setup or is already in place.
230
fn ensure_external_tools(opt Options) ! {
231
	if !os.exists(cache_dir) {
232
		os.mkdir_all(cache_dir)!
233
	}
234
	if opt.force_update {
235
		download_shdc(opt)!
236
		return
237
	}
238

239
	is_shdc_available := os.is_file(shdc_exe)
240
	is_shdc_executable := os.is_executable(shdc_exe)
241
	if opt.verbose {
242
		eprintln('reading version from ${shdc_version_file} ...')
243
		version := os.read_file(shdc_version_file) or { 'unknown' }
244
		eprintln('${tool_name} using sokol-shdc version ${version} at "${shdc_exe}"')
245
		eprintln('executable: ${is_shdc_executable}')
246
		eprintln(' available: ${is_shdc_available}')
247
		if is_shdc_available {
248
			eprintln(' file path: ${shdc_exe}')
249
			eprintln(' file size: ${os.file_size(shdc_exe)}')
250
			eprintln(' file time: ${time.unix_microsecond(os.file_last_mod_unix(shdc_exe),
251
				0)}')
252
		}
253
	}
254
	if is_shdc_available && is_shdc_executable {
255
		return
256
	}
257

258
	download_shdc(opt)!
259
}
260

261
// download_shdc downloads the `sokol-shdc` tool to an OS specific cache directory.
262
fn download_shdc(opt Options) ! {
263
	// We want to use the same, runtime, OS type as this tool is invoked on.
264
	mut download_url := shdc_urls[runtime_os] or { '' }
265
	$if arm64 && macos {
266
		download_url = shdc_urls['osx_a64']
267
	}
268
	if download_url == '' {
269
		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}')
270
	}
271
	if opt.verbose {
272
		eprintln('> reading version from ${shdc_version_file} ...')
273
	}
274
	update_to_shdc_version := os.read_file(shdc_version_file) or { shdc_version }
275
	if opt.verbose {
276
		eprintln('> update_to_shdc_version: ${update_to_shdc_version} | shdc_version: ${shdc_version}')
277
	}
278
	if opt.verbose {
279
		if shdc_version != update_to_shdc_version && os.exists(shdc_exe) {
280
			eprintln('${tool_name} updating sokol-shdc to version ${shdc_version} ...')
281
		} else {
282
			eprintln('${tool_name} installing sokol-shdc version ${update_to_shdc_version} ...')
283
		}
284
	}
285
	if os.exists(shdc_exe) {
286
		os.rm(shdc_exe)!
287
	}
288

289
	mut dtmp_file, dtmp_path := util.temp_file(util.TempFileOptions{ path: os.dir(shdc_exe) })!
290
	dtmp_file.close()
291
	if opt.verbose {
292
		eprintln('${tool_name} downloading sokol-shdc from ${download_url}')
293
	}
294
	http.download_file(download_url, dtmp_path) or {
295
		os.rm(dtmp_path)!
296
		return error('${tool_name} failed to download sokol-shdc needed for shader compiling: ${err}')
297
	}
298
	// Make it executable
299
	os.chmod(dtmp_path, 0o775)!
300
	// Move downloaded file in place
301
	os.mv(dtmp_path, shdc_exe)!
302
	// Update internal version file
303
	os.write_file(shdc_version_file, shdc_version)!
304
}
305

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.