1use librashader_common::map::FastHashMap;
8use once_cell::sync::Lazy;
9use regex::bytes::Regex;
10use std::collections::VecDeque;
11use std::fmt::{Debug, Display, Formatter};
12use std::ops::Add;
13use std::path::{Component, Path, PathBuf};
14
15#[repr(u32)]
17#[non_exhaustive]
18#[derive(Debug, Copy, Clone)]
19pub enum VideoDriver {
20 None = 0,
22 GlCore,
24 Gl,
26 Vulkan,
28 Direct3D9Hlsl,
30 Direct3D11,
32 Direct3D12,
34 Metal,
36}
37
38impl Display for VideoDriver {
39 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
40 match self {
41 VideoDriver::None => f.write_str("null"),
42 VideoDriver::GlCore => f.write_str("glcore"),
43 VideoDriver::Gl => f.write_str("gl"),
44 VideoDriver::Vulkan => f.write_str("vulkan"),
45 VideoDriver::Direct3D11 => f.write_str("d3d11"),
46 VideoDriver::Direct3D9Hlsl => f.write_str("d3d9_hlsl"),
47 VideoDriver::Direct3D12 => f.write_str("d3d12"),
48 VideoDriver::Metal => f.write_str("metal"),
49 }
50 }
51}
52
53#[repr(u32)]
55#[derive(Debug, Copy, Clone)]
56pub enum ShaderExtension {
57 Slang = 0,
59 Glsl,
61 Cg,
63}
64
65impl Display for ShaderExtension {
66 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
67 match self {
68 ShaderExtension::Slang => f.write_str("slang"),
69 ShaderExtension::Glsl => f.write_str("glsl"),
70 ShaderExtension::Cg => f.write_str("cg"),
71 }
72 }
73}
74
75#[repr(u32)]
77#[derive(Debug, Copy, Clone)]
78pub enum PresetExtension {
79 Slangp = 0,
81 Glslp,
83 Cgp,
85}
86
87impl Display for PresetExtension {
88 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
89 match self {
90 PresetExtension::Slangp => f.write_str("slangp"),
91 PresetExtension::Glslp => f.write_str("glslp"),
92 PresetExtension::Cgp => f.write_str("cgp"),
93 }
94 }
95}
96
97#[repr(u32)]
99#[derive(Debug, Copy, Clone)]
100pub enum Rotation {
101 Zero = 0,
103 Right = 1,
105 Straight = 2,
107 Reflex = 3,
109}
110
111impl From<u32> for Rotation {
112 fn from(value: u32) -> Self {
113 let value = value % 4;
114 match value {
115 0 => Rotation::Zero,
116 1 => Rotation::Right,
117 2 => Rotation::Straight,
118 3 => Rotation::Reflex,
119 _ => unreachable!(),
120 }
121 }
122}
123
124impl Add for Rotation {
125 type Output = Rotation;
126
127 fn add(self, rhs: Self) -> Self::Output {
128 let lhs = self as u32;
129 let out = lhs + rhs as u32;
130 Rotation::from(out)
131 }
132}
133
134impl Display for Rotation {
135 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
136 match self {
137 Rotation::Zero => f.write_str("0"),
138 Rotation::Right => f.write_str("90"),
139 Rotation::Straight => f.write_str("180"),
140 Rotation::Reflex => f.write_str("270"),
141 }
142 }
143}
144
145#[repr(u32)]
147#[derive(Debug, Copy, Clone)]
148pub enum Orientation {
149 Vertical = 0,
151 Horizontal,
153}
154
155impl Display for Orientation {
156 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
157 match self {
158 Orientation::Vertical => f.write_str("VERT"),
159 Orientation::Horizontal => f.write_str("HORZ"),
160 }
161 }
162}
163
164#[derive(Debug, Clone)]
166pub enum ContextItem {
167 ContentDirectory(String),
169 CoreName(String),
171 GameName(String),
173 Preset(String),
175 PresetDirectory(String),
177 VideoDriver(VideoDriver),
179 VideoDriverShaderExtension(ShaderExtension),
181 VideoDriverPresetExtension(PresetExtension),
183 CoreRequestedRotation(Rotation),
185 AllowCoreRotation(bool),
187 UserRotation(Rotation),
189 FinalRotation(Rotation),
191 ScreenOrientation(Rotation),
193 ViewAspectOrientation(Orientation),
195 CoreAspectOrientation(Orientation),
197 ExternContext(String, String),
199}
200
201impl ContextItem {
202 fn toggle_str(v: bool) -> &'static str {
203 if v {
204 "ON"
205 } else {
206 "OFF"
207 }
208 }
209
210 pub fn key(&self) -> &str {
212 match self {
213 ContextItem::ContentDirectory(_) => "CONTENT-DIR",
214 ContextItem::CoreName(_) => "CORE",
215 ContextItem::GameName(_) => "GAME",
216 ContextItem::Preset(_) => "PRESET",
217 ContextItem::PresetDirectory(_) => "PRESET_DIR",
218 ContextItem::VideoDriver(_) => "VID-DRV",
219 ContextItem::CoreRequestedRotation(_) => "CORE-REQ-ROT",
220 ContextItem::AllowCoreRotation(_) => "VID-ALLOW-CORE-ROT",
221 ContextItem::UserRotation(_) => "VID-USER-ROT",
222 ContextItem::FinalRotation(_) => "VID-FINAL-ROT",
223 ContextItem::ScreenOrientation(_) => "SCREEN-ORIENT",
224 ContextItem::ViewAspectOrientation(_) => "VIEW-ASPECT-ORIENT",
225 ContextItem::CoreAspectOrientation(_) => "CORE-ASPECT-ORIENT",
226 ContextItem::VideoDriverShaderExtension(_) => "VID-DRV-SHADER-EXT",
227 ContextItem::VideoDriverPresetExtension(_) => "VID-DRV-PRESET-EXT",
228 ContextItem::ExternContext(key, _) => key,
229 }
230 }
231}
232
233impl Display for ContextItem {
234 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
235 match self {
236 ContextItem::ContentDirectory(v) => f.write_str(v),
237 ContextItem::CoreName(v) => f.write_str(v),
238 ContextItem::GameName(v) => f.write_str(v),
239 ContextItem::Preset(v) => f.write_str(v),
240 ContextItem::PresetDirectory(v) => f.write_str(v),
241 ContextItem::VideoDriver(v) => f.write_fmt(format_args!("{}", v)),
242 ContextItem::CoreRequestedRotation(v) => {
243 f.write_fmt(format_args!("{}-{}", self.key(), v))
244 }
245 ContextItem::AllowCoreRotation(v) => f.write_fmt(format_args!(
246 "{}-{}",
247 self.key(),
248 ContextItem::toggle_str(*v)
249 )),
250 ContextItem::UserRotation(v) => f.write_fmt(format_args!("{}-{}", self.key(), v)),
251 ContextItem::FinalRotation(v) => f.write_fmt(format_args!("{}-{}", self.key(), v)),
252 ContextItem::ScreenOrientation(v) => f.write_fmt(format_args!("{}-{}", self.key(), v)),
253 ContextItem::ViewAspectOrientation(v) => {
254 f.write_fmt(format_args!("{}-{}", self.key(), v))
255 }
256 ContextItem::CoreAspectOrientation(v) => {
257 f.write_fmt(format_args!("{}-{}", self.key(), v))
258 }
259 ContextItem::VideoDriverShaderExtension(v) => f.write_fmt(format_args!("{}", v)),
260 ContextItem::VideoDriverPresetExtension(v) => f.write_fmt(format_args!("{}", v)),
261 ContextItem::ExternContext(_, v) => f.write_fmt(format_args!("{}", v)),
262 }
263 }
264}
265
266#[derive(Debug, Clone)]
277pub struct WildcardContext(VecDeque<ContextItem>);
278
279impl WildcardContext {
280 pub fn new() -> Self {
282 Self(VecDeque::new())
283 }
284
285 pub fn prepend_item(&mut self, item: ContextItem) {
287 self.0.push_front(item);
288 }
289
290 pub fn append_item(&mut self, item: ContextItem) {
293 self.0.push_back(item);
294 }
295
296 pub fn add_video_driver_defaults(&mut self, video_driver: VideoDriver) {
300 self.prepend_item(ContextItem::VideoDriverPresetExtension(
301 PresetExtension::Slangp,
302 ));
303 self.prepend_item(ContextItem::VideoDriverShaderExtension(
304 ShaderExtension::Slang,
305 ));
306 self.prepend_item(ContextItem::VideoDriver(video_driver));
307 }
308
309 pub fn add_path_defaults(&mut self, path: impl AsRef<Path>) {
313 let path = path.as_ref();
314 if let Some(preset_name) = path.file_stem() {
315 let preset_name = preset_name.to_string_lossy();
316 self.prepend_item(ContextItem::Preset(preset_name.into()))
317 }
318
319 if let Some(preset_dir_name) = path.parent().and_then(|p| {
320 if !p.is_dir() {
321 return None;
322 };
323 p.file_name()
324 }) {
325 let preset_dir_name = preset_dir_name.to_string_lossy();
326 self.prepend_item(ContextItem::PresetDirectory(preset_dir_name.into()))
327 }
328 }
329
330 pub fn into_hashmap(mut self) -> FastHashMap<String, String> {
335 let mut map = FastHashMap::default();
336 let last_user_rot = self
337 .0
338 .iter()
339 .rfind(|i| matches!(i, ContextItem::UserRotation(_)));
340 let last_core_rot = self
341 .0
342 .iter()
343 .rfind(|i| matches!(i, ContextItem::CoreRequestedRotation(_)));
344
345 let final_rot = match (last_core_rot, last_user_rot) {
346 (Some(ContextItem::UserRotation(u)), None) => Some(ContextItem::FinalRotation(*u)),
347 (None, Some(ContextItem::CoreRequestedRotation(c))) => {
348 Some(ContextItem::FinalRotation(*c))
349 }
350 (Some(ContextItem::UserRotation(u)), Some(ContextItem::CoreRequestedRotation(c))) => {
351 Some(ContextItem::FinalRotation(*u + *c))
352 }
353 _ => None,
354 };
355
356 if let Some(final_rot) = final_rot {
357 self.prepend_item(final_rot);
358 }
359
360 for item in self.0 {
361 map.insert(String::from(item.key()), item.to_string());
362 }
363
364 map
365 }
366}
367
368pub(crate) fn apply_context(path: &mut PathBuf, context: &FastHashMap<String, String>) {
369 use std::ffi::{OsStr, OsString};
370
371 static WILDCARD_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new("\\$([A-Z-_]+)\\$").unwrap());
372 if context.is_empty() {
373 return;
374 }
375 if !WILDCARD_REGEX.is_match(path.as_os_str().as_encoded_bytes()) {
377 return;
378 }
379
380 let mut new_path = PathBuf::with_capacity(path.capacity());
381 for component in path.components() {
382 match component {
383 Component::Normal(path) => {
384 let haystack = path.as_encoded_bytes();
385
386 let replaced =
387 WILDCARD_REGEX.replace_all(haystack, |caps: ®ex::bytes::Captures| {
388 let Some(name) = caps.get(1) else {
389 return caps[0].to_vec();
390 };
391
392 let Ok(key) = std::str::from_utf8(name.as_bytes()) else {
393 return caps[0].to_vec();
394 };
395 if let Some(replacement) = context.get(key) {
396 return OsString::from(replacement.to_string()).into_encoded_bytes();
397 }
398 return caps[0].to_vec();
399 });
400
401 new_path.push(unsafe { OsStr::from_encoded_bytes_unchecked(&replaced.as_ref()) })
404 }
405 _ => new_path.push(component),
406 }
407 }
408
409 if let Ok(true) = new_path.try_exists() {
411 *path = new_path;
412 }
413}