1#[allow(dead_code, unused_variables, unused_mut, unused_imports)]
2
3use glam::{Vec2, Vec3, Vec4, Quat, Mat4};
4use std::collections::{HashMap, VecDeque, HashSet, BTreeMap};
5
6#[derive(Debug, Clone, PartialEq)]
11pub enum TokenKind {
12 IntLit(i64),
14 FloatLit(f64),
15 BoolLit(bool),
16 StringLit(String),
17 Ident(String),
19 KwFloat, KwInt, KwUint, KwBool,
21 KwVec2, KwVec3, KwVec4,
22 KwIVec2, KwIVec3, KwIVec4,
23 KwUVec2, KwUVec3, KwUVec4,
24 KwMat2, KwMat3, KwMat4,
25 KwSampler2D, KwSampler3D, KwSamplerCube,
26 KwVoid,
27 KwIf, KwElse, KwFor, KwWhile, KwDo, KwReturn, KwBreak, KwContinue,
29 KwStruct, KwIn, KwOut, KwInout, KwUniform, KwConst, KwLayout,
30 KwAttribute, KwVarying, KwPrecision, KwHighp, KwMediump, KwLowp,
31 KwDiscard,
32 Plus, Minus, Star, Slash, Percent,
34 PlusEq, MinusEq, StarEq, SlashEq, PercentEq,
35 PlusPlus, MinusMinus,
36 Amp, Pipe, Caret, Tilde, LShift, RShift,
37 AmpEq, PipeEq, CaretEq, LShiftEq, RShiftEq,
38 Eq, EqEq, BangEq,
39 Lt, Gt, LtEq, GtEq,
40 AmpAmp, PipePipe, Bang,
41 Question, Colon,
42 Dot, Arrow,
43 Semicolon, Comma,
44 LParen, RParen,
45 LBrace, RBrace,
46 LBracket, RBracket,
47 Hash,
48 Eof,
50 Newline,
51}
52
53#[derive(Debug, Clone)]
54pub struct Token {
55 pub kind: TokenKind,
56 pub line: u32,
57 pub col: u32,
58 pub source: String,
59}
60
61impl Token {
62 pub fn new(kind: TokenKind, line: u32, col: u32, source: &str) -> Self {
63 Self { kind, line, col, source: source.to_string() }
64 }
65 pub fn is_type_keyword(&self) -> bool {
66 matches!(self.kind,
67 TokenKind::KwFloat | TokenKind::KwInt | TokenKind::KwUint | TokenKind::KwBool |
68 TokenKind::KwVec2 | TokenKind::KwVec3 | TokenKind::KwVec4 |
69 TokenKind::KwMat2 | TokenKind::KwMat3 | TokenKind::KwMat4 |
70 TokenKind::KwSampler2D | TokenKind::KwSampler3D | TokenKind::KwSamplerCube |
71 TokenKind::KwVoid)
72 }
73}
74
75pub struct Lexer {
80 pub source: Vec<char>,
81 pub pos: usize,
82 pub line: u32,
83 pub col: u32,
84}
85
86impl Lexer {
87 pub fn new(source: &str) -> Self {
88 Self { source: source.chars().collect(), pos: 0, line: 1, col: 1 }
89 }
90
91 fn peek(&self) -> Option<char> { self.source.get(self.pos).copied() }
92 fn peek2(&self) -> Option<char> { self.source.get(self.pos + 1).copied() }
93
94 fn advance(&mut self) -> Option<char> {
95 let c = self.source.get(self.pos).copied()?;
96 self.pos += 1;
97 if c == '\n' { self.line += 1; self.col = 1; } else { self.col += 1; }
98 Some(c)
99 }
100
101 fn skip_whitespace(&mut self) {
102 while let Some(c) = self.peek() {
103 if c == ' ' || c == '\t' || c == '\r' { self.advance(); }
104 else if c == '\n' { self.advance(); }
105 else if c == '/' && self.peek2() == Some('/') {
106 while let Some(c) = self.peek() { if c == '\n' { break; } self.advance(); }
107 } else if c == '/' && self.peek2() == Some('*') {
108 self.advance(); self.advance();
109 loop {
110 match self.advance() {
111 None => break,
112 Some('*') if self.peek() == Some('/') => { self.advance(); break; }
113 _ => {}
114 }
115 }
116 } else { break; }
117 }
118 }
119
120 fn read_ident(&mut self) -> String {
121 let mut s = String::new();
122 while let Some(c) = self.peek() {
123 if c.is_alphanumeric() || c == '_' { s.push(c); self.advance(); } else { break; }
124 }
125 s
126 }
127
128 fn read_number(&mut self) -> TokenKind {
129 let mut s = String::new();
130 let mut is_float = false;
131 while let Some(c) = self.peek() {
132 if c.is_ascii_digit() { s.push(c); self.advance(); }
133 else if c == '.' && !is_float {
134 is_float = true; s.push(c); self.advance();
135 } else if (c == 'e' || c == 'E') && is_float {
136 s.push(c); self.advance();
137 if let Some('+') | Some('-') = self.peek() {
138 s.push(self.advance().unwrap());
139 }
140 } else if c == 'f' || c == 'u' { self.advance(); break; }
141 else { break; }
142 }
143 if is_float {
144 TokenKind::FloatLit(s.parse().unwrap_or(0.0))
145 } else {
146 if s.starts_with("0x") || s.starts_with("0X") {
147 TokenKind::IntLit(i64::from_str_radix(&s[2..], 16).unwrap_or(0))
148 } else {
149 TokenKind::IntLit(s.parse().unwrap_or(0))
150 }
151 }
152 }
153
154 fn ident_to_keyword(s: &str) -> TokenKind {
155 match s {
156 "float" => TokenKind::KwFloat,
157 "int" => TokenKind::KwInt,
158 "uint" => TokenKind::KwUint,
159 "bool" => TokenKind::KwBool,
160 "vec2" => TokenKind::KwVec2,
161 "vec3" => TokenKind::KwVec3,
162 "vec4" => TokenKind::KwVec4,
163 "ivec2" => TokenKind::KwIVec2,
164 "ivec3" => TokenKind::KwIVec3,
165 "ivec4" => TokenKind::KwIVec4,
166 "uvec2" => TokenKind::KwUVec2,
167 "uvec3" => TokenKind::KwUVec3,
168 "uvec4" => TokenKind::KwUVec4,
169 "mat2" => TokenKind::KwMat2,
170 "mat3" => TokenKind::KwMat3,
171 "mat4" => TokenKind::KwMat4,
172 "sampler2D" => TokenKind::KwSampler2D,
173 "sampler3D" => TokenKind::KwSampler3D,
174 "samplerCube" => TokenKind::KwSamplerCube,
175 "void" => TokenKind::KwVoid,
176 "if" => TokenKind::KwIf,
177 "else" => TokenKind::KwElse,
178 "for" => TokenKind::KwFor,
179 "while" => TokenKind::KwWhile,
180 "do" => TokenKind::KwDo,
181 "return" => TokenKind::KwReturn,
182 "break" => TokenKind::KwBreak,
183 "continue" => TokenKind::KwContinue,
184 "struct" => TokenKind::KwStruct,
185 "in" => TokenKind::KwIn,
186 "out" => TokenKind::KwOut,
187 "inout" => TokenKind::KwInout,
188 "uniform" => TokenKind::KwUniform,
189 "const" => TokenKind::KwConst,
190 "layout" => TokenKind::KwLayout,
191 "attribute" => TokenKind::KwAttribute,
192 "varying" => TokenKind::KwVarying,
193 "precision" => TokenKind::KwPrecision,
194 "highp" => TokenKind::KwHighp,
195 "mediump" => TokenKind::KwMediump,
196 "lowp" => TokenKind::KwLowp,
197 "discard" => TokenKind::KwDiscard,
198 "true" => TokenKind::BoolLit(true),
199 "false" => TokenKind::BoolLit(false),
200 other => TokenKind::Ident(other.to_string()),
201 }
202 }
203
204 pub fn tokenize(&mut self) -> Vec<Token> {
205 let mut tokens = Vec::new();
206 loop {
207 self.skip_whitespace();
208 let line = self.line;
209 let col = self.col;
210 match self.peek() {
211 None => { tokens.push(Token::new(TokenKind::Eof, line, col, "")); break; }
212 Some(c) => {
213 let tok = match c {
214 'a'..='z' | 'A'..='Z' | '_' => {
215 let s = self.read_ident();
216 let kind = Self::ident_to_keyword(&s);
217 Token::new(kind, line, col, &s)
218 }
219 '0'..='9' => {
220 let kind = self.read_number();
221 Token::new(kind, line, col, "")
222 }
223 '+' => {
224 self.advance();
225 if self.peek() == Some('+') { self.advance(); Token::new(TokenKind::PlusPlus, line, col, "++") }
226 else if self.peek() == Some('=') { self.advance(); Token::new(TokenKind::PlusEq, line, col, "+=") }
227 else { Token::new(TokenKind::Plus, line, col, "+") }
228 }
229 '-' => {
230 self.advance();
231 if self.peek() == Some('-') { self.advance(); Token::new(TokenKind::MinusMinus, line, col, "--") }
232 else if self.peek() == Some('=') { self.advance(); Token::new(TokenKind::MinusEq, line, col, "-=") }
233 else if self.peek() == Some('>') { self.advance(); Token::new(TokenKind::Arrow, line, col, "->") }
234 else { Token::new(TokenKind::Minus, line, col, "-") }
235 }
236 '*' => {
237 self.advance();
238 if self.peek() == Some('=') { self.advance(); Token::new(TokenKind::StarEq, line, col, "*=") }
239 else { Token::new(TokenKind::Star, line, col, "*") }
240 }
241 '/' => {
242 self.advance();
243 if self.peek() == Some('=') { self.advance(); Token::new(TokenKind::SlashEq, line, col, "/=") }
244 else { Token::new(TokenKind::Slash, line, col, "/") }
245 }
246 '%' => {
247 self.advance();
248 if self.peek() == Some('=') { self.advance(); Token::new(TokenKind::PercentEq, line, col, "%=") }
249 else { Token::new(TokenKind::Percent, line, col, "%") }
250 }
251 '=' => {
252 self.advance();
253 if self.peek() == Some('=') { self.advance(); Token::new(TokenKind::EqEq, line, col, "==") }
254 else { Token::new(TokenKind::Eq, line, col, "=") }
255 }
256 '!' => {
257 self.advance();
258 if self.peek() == Some('=') { self.advance(); Token::new(TokenKind::BangEq, line, col, "!=") }
259 else { Token::new(TokenKind::Bang, line, col, "!") }
260 }
261 '<' => {
262 self.advance();
263 if self.peek() == Some('=') { self.advance(); Token::new(TokenKind::LtEq, line, col, "<=") }
264 else if self.peek() == Some('<') {
265 self.advance();
266 if self.peek() == Some('=') { self.advance(); Token::new(TokenKind::LShiftEq, line, col, "<<=") }
267 else { Token::new(TokenKind::LShift, line, col, "<<") }
268 }
269 else { Token::new(TokenKind::Lt, line, col, "<") }
270 }
271 '>' => {
272 self.advance();
273 if self.peek() == Some('=') { self.advance(); Token::new(TokenKind::GtEq, line, col, ">=") }
274 else if self.peek() == Some('>') {
275 self.advance();
276 if self.peek() == Some('=') { self.advance(); Token::new(TokenKind::RShiftEq, line, col, ">>=") }
277 else { Token::new(TokenKind::RShift, line, col, ">>") }
278 }
279 else { Token::new(TokenKind::Gt, line, col, ">") }
280 }
281 '&' => {
282 self.advance();
283 if self.peek() == Some('&') { self.advance(); Token::new(TokenKind::AmpAmp, line, col, "&&") }
284 else if self.peek() == Some('=') { self.advance(); Token::new(TokenKind::AmpEq, line, col, "&=") }
285 else { Token::new(TokenKind::Amp, line, col, "&") }
286 }
287 '|' => {
288 self.advance();
289 if self.peek() == Some('|') { self.advance(); Token::new(TokenKind::PipePipe, line, col, "||") }
290 else if self.peek() == Some('=') { self.advance(); Token::new(TokenKind::PipeEq, line, col, "|=") }
291 else { Token::new(TokenKind::Pipe, line, col, "|") }
292 }
293 '^' => {
294 self.advance();
295 if self.peek() == Some('=') { self.advance(); Token::new(TokenKind::CaretEq, line, col, "^=") }
296 else { Token::new(TokenKind::Caret, line, col, "^") }
297 }
298 '~' => { self.advance(); Token::new(TokenKind::Tilde, line, col, "~") }
299 '?' => { self.advance(); Token::new(TokenKind::Question, line, col, "?") }
300 ':' => { self.advance(); Token::new(TokenKind::Colon, line, col, ":") }
301 '.' => { self.advance(); Token::new(TokenKind::Dot, line, col, ".") }
302 ';' => { self.advance(); Token::new(TokenKind::Semicolon, line, col, ";") }
303 ',' => { self.advance(); Token::new(TokenKind::Comma, line, col, ",") }
304 '(' => { self.advance(); Token::new(TokenKind::LParen, line, col, "(") }
305 ')' => { self.advance(); Token::new(TokenKind::RParen, line, col, ")") }
306 '{' => { self.advance(); Token::new(TokenKind::LBrace, line, col, "{") }
307 '}' => { self.advance(); Token::new(TokenKind::RBrace, line, col, "}") }
308 '[' => { self.advance(); Token::new(TokenKind::LBracket, line, col, "[") }
309 ']' => { self.advance(); Token::new(TokenKind::RBracket, line, col, "]") }
310 '#' => { self.advance(); Token::new(TokenKind::Hash, line, col, "#") }
311 _ => { self.advance(); continue; }
312 };
313 tokens.push(tok);
314 }
315 }
316 }
317 tokens
318 }
319}
320
321#[derive(Debug, Clone, PartialEq, Eq, Hash)]
326pub enum ShaderType {
327 Void,
328 Bool,
329 Int,
330 Uint,
331 Float,
332 BVec2, BVec3, BVec4,
333 IVec2, IVec3, IVec4,
334 UVec2, UVec3, UVec4,
335 Vec2, Vec3, Vec4,
336 Mat2, Mat3, Mat4,
337 Sampler2D, Sampler3D, SamplerCube,
338 Struct(String),
339 Array(Box<ShaderType>, Option<u32>), Function(Vec<ShaderType>, Box<ShaderType>), Unknown,
342}
343
344impl ShaderType {
345 pub fn is_scalar(&self) -> bool {
346 matches!(self, ShaderType::Bool | ShaderType::Int | ShaderType::Uint | ShaderType::Float)
347 }
348 pub fn is_vector(&self) -> bool {
349 matches!(self,
350 ShaderType::BVec2|ShaderType::BVec3|ShaderType::BVec4|
351 ShaderType::IVec2|ShaderType::IVec3|ShaderType::IVec4|
352 ShaderType::UVec2|ShaderType::UVec3|ShaderType::UVec4|
353 ShaderType::Vec2|ShaderType::Vec3|ShaderType::Vec4)
354 }
355 pub fn is_matrix(&self) -> bool {
356 matches!(self, ShaderType::Mat2|ShaderType::Mat3|ShaderType::Mat4)
357 }
358 pub fn is_numeric(&self) -> bool {
359 matches!(self,
360 ShaderType::Int|ShaderType::Uint|ShaderType::Float|
361 ShaderType::IVec2|ShaderType::IVec3|ShaderType::IVec4|
362 ShaderType::UVec2|ShaderType::UVec3|ShaderType::UVec4|
363 ShaderType::Vec2|ShaderType::Vec3|ShaderType::Vec4|
364 ShaderType::Mat2|ShaderType::Mat3|ShaderType::Mat4)
365 }
366 pub fn component_count(&self) -> u32 {
367 match self {
368 ShaderType::Float|ShaderType::Int|ShaderType::Uint|ShaderType::Bool => 1,
369 ShaderType::Vec2|ShaderType::IVec2|ShaderType::UVec2|ShaderType::BVec2 => 2,
370 ShaderType::Vec3|ShaderType::IVec3|ShaderType::UVec3|ShaderType::BVec3 => 3,
371 ShaderType::Vec4|ShaderType::IVec4|ShaderType::UVec4|ShaderType::BVec4 => 4,
372 ShaderType::Mat2 => 4,
373 ShaderType::Mat3 => 9,
374 ShaderType::Mat4 => 16,
375 _ => 0,
376 }
377 }
378 pub fn base_scalar(&self) -> ShaderType {
379 match self {
380 ShaderType::Vec2|ShaderType::Vec3|ShaderType::Vec4 => ShaderType::Float,
381 ShaderType::IVec2|ShaderType::IVec3|ShaderType::IVec4 => ShaderType::Int,
382 ShaderType::UVec2|ShaderType::UVec3|ShaderType::UVec4 => ShaderType::Uint,
383 ShaderType::BVec2|ShaderType::BVec3|ShaderType::BVec4 => ShaderType::Bool,
384 ShaderType::Mat2|ShaderType::Mat3|ShaderType::Mat4 => ShaderType::Float,
385 other => other.clone(),
386 }
387 }
388 pub fn name(&self) -> &str {
389 match self {
390 ShaderType::Void => "void",
391 ShaderType::Bool => "bool",
392 ShaderType::Int => "int",
393 ShaderType::Uint => "uint",
394 ShaderType::Float => "float",
395 ShaderType::BVec2 => "bvec2",
396 ShaderType::BVec3 => "bvec3",
397 ShaderType::BVec4 => "bvec4",
398 ShaderType::IVec2 => "ivec2",
399 ShaderType::IVec3 => "ivec3",
400 ShaderType::IVec4 => "ivec4",
401 ShaderType::UVec2 => "uvec2",
402 ShaderType::UVec3 => "uvec3",
403 ShaderType::UVec4 => "uvec4",
404 ShaderType::Vec2 => "vec2",
405 ShaderType::Vec3 => "vec3",
406 ShaderType::Vec4 => "vec4",
407 ShaderType::Mat2 => "mat2",
408 ShaderType::Mat3 => "mat3",
409 ShaderType::Mat4 => "mat4",
410 ShaderType::Sampler2D => "sampler2D",
411 ShaderType::Sampler3D => "sampler3D",
412 ShaderType::SamplerCube => "samplerCube",
413 ShaderType::Struct(n) => n.as_str(),
414 ShaderType::Array(_, _) => "array",
415 ShaderType::Function(_, _) => "function",
416 ShaderType::Unknown => "unknown",
417 }
418 }
419 pub fn can_implicit_convert(from: &ShaderType, to: &ShaderType) -> bool {
421 if from == to { return true; }
422 matches!((from, to),
423 (ShaderType::Int, ShaderType::Float) |
424 (ShaderType::Uint, ShaderType::Float) |
425 (ShaderType::Int, ShaderType::Uint) |
426 (ShaderType::Bool, ShaderType::Int) |
427 (ShaderType::Bool, ShaderType::Float) |
428 (ShaderType::IVec2, ShaderType::Vec2) |
429 (ShaderType::IVec3, ShaderType::Vec3) |
430 (ShaderType::IVec4, ShaderType::Vec4)
431 )
432 }
433 pub fn from_token(t: &TokenKind) -> Option<ShaderType> {
434 match t {
435 TokenKind::KwFloat => Some(ShaderType::Float),
436 TokenKind::KwInt => Some(ShaderType::Int),
437 TokenKind::KwUint => Some(ShaderType::Uint),
438 TokenKind::KwBool => Some(ShaderType::Bool),
439 TokenKind::KwVec2 => Some(ShaderType::Vec2),
440 TokenKind::KwVec3 => Some(ShaderType::Vec3),
441 TokenKind::KwVec4 => Some(ShaderType::Vec4),
442 TokenKind::KwIVec2 => Some(ShaderType::IVec2),
443 TokenKind::KwIVec3 => Some(ShaderType::IVec3),
444 TokenKind::KwIVec4 => Some(ShaderType::IVec4),
445 TokenKind::KwUVec2 => Some(ShaderType::UVec2),
446 TokenKind::KwUVec3 => Some(ShaderType::UVec3),
447 TokenKind::KwUVec4 => Some(ShaderType::UVec4),
448 TokenKind::KwMat2 => Some(ShaderType::Mat2),
449 TokenKind::KwMat3 => Some(ShaderType::Mat3),
450 TokenKind::KwMat4 => Some(ShaderType::Mat4),
451 TokenKind::KwSampler2D => Some(ShaderType::Sampler2D),
452 TokenKind::KwSampler3D => Some(ShaderType::Sampler3D),
453 TokenKind::KwSamplerCube => Some(ShaderType::SamplerCube),
454 TokenKind::KwVoid => Some(ShaderType::Void),
455 _ => None,
456 }
457 }
458}
459
460#[derive(Debug, Clone)]
465pub enum Expr {
466 IntLit(i64),
467 FloatLit(f64),
468 BoolLit(bool),
469 Ident(String),
470 Unary { op: UnaryOp, operand: Box<Expr> },
471 Binary { op: BinaryOp, left: Box<Expr>, right: Box<Expr> },
472 Assign { op: AssignOp, target: Box<Expr>, value: Box<Expr> },
473 Ternary { cond: Box<Expr>, then_expr: Box<Expr>, else_expr: Box<Expr> },
474 Call { function: String, args: Vec<Expr> },
475 Index { array: Box<Expr>, index: Box<Expr> },
476 Field { object: Box<Expr>, field: String },
477 Swizzle { object: Box<Expr>, components: String },
478 Cast { target_type: ShaderType, expr: Box<Expr> },
479 Construction { ty: ShaderType, args: Vec<Expr> },
480}
481
482#[derive(Debug, Clone, Copy, PartialEq)]
483pub enum UnaryOp {
484 Negate,
485 Not,
486 BitNot,
487 PreIncrement,
488 PreDecrement,
489 PostIncrement,
490 PostDecrement,
491}
492
493#[derive(Debug, Clone, Copy, PartialEq)]
494pub enum BinaryOp {
495 Add, Sub, Mul, Div, Mod,
496 Eq, Ne, Lt, Gt, Le, Ge,
497 And, Or,
498 BitAnd, BitOr, BitXor, Shl, Shr,
499}
500
501#[derive(Debug, Clone, Copy, PartialEq)]
502pub enum AssignOp {
503 Assign, AddAssign, SubAssign, MulAssign, DivAssign, ModAssign,
504 AndAssign, OrAssign, XorAssign, ShlAssign, ShrAssign,
505}
506
507#[derive(Debug, Clone)]
508pub enum Stmt {
509 Expr(Expr),
510 Block(Vec<Stmt>),
511 VarDecl { ty: ShaderType, name: String, qualifier: Option<VarQualifier>, init: Option<Expr> },
512 If { cond: Expr, then_body: Box<Stmt>, else_body: Option<Box<Stmt>> },
513 For { init: Option<Box<Stmt>>, cond: Option<Expr>, step: Option<Expr>, body: Box<Stmt> },
514 While { cond: Expr, body: Box<Stmt> },
515 DoWhile { body: Box<Stmt>, cond: Expr },
516 Return(Option<Expr>),
517 Break,
518 Continue,
519 Discard,
520}
521
522#[derive(Debug, Clone, Copy, PartialEq)]
523pub enum VarQualifier {
524 In, Out, Inout, Uniform, Const, Attribute, Varying,
525}
526
527#[derive(Debug, Clone)]
528pub struct FunctionParam {
529 pub ty: ShaderType,
530 pub name: String,
531 pub qualifier: Option<VarQualifier>,
532}
533
534#[derive(Debug, Clone)]
535pub struct FunctionDecl {
536 pub return_type: ShaderType,
537 pub name: String,
538 pub params: Vec<FunctionParam>,
539 pub body: Vec<Stmt>,
540 pub is_builtin: bool,
541}
542
543#[derive(Debug, Clone)]
544pub struct StructField {
545 pub ty: ShaderType,
546 pub name: String,
547 pub array_size: Option<u32>,
548}
549
550#[derive(Debug, Clone)]
551pub struct StructDecl {
552 pub name: String,
553 pub fields: Vec<StructField>,
554}
555
556#[derive(Debug, Clone)]
557pub struct UniformDecl {
558 pub ty: ShaderType,
559 pub name: String,
560 pub binding: Option<u32>,
561 pub set: Option<u32>,
562 pub array_size: Option<u32>,
563}
564
565#[derive(Debug, Clone)]
566pub struct AttributeDecl {
567 pub ty: ShaderType,
568 pub name: String,
569 pub location: Option<u32>,
570}
571
572#[derive(Debug, Clone)]
573pub struct ShaderAst {
574 pub version: u32,
575 pub extensions: Vec<String>,
576 pub structs: Vec<StructDecl>,
577 pub uniforms: Vec<UniformDecl>,
578 pub attributes: Vec<AttributeDecl>,
579 pub varyings: Vec<AttributeDecl>,
580 pub functions: Vec<FunctionDecl>,
581 pub global_vars: Vec<Stmt>,
582 pub defines: HashMap<String, String>,
583}
584
585impl ShaderAst {
586 pub fn new() -> Self {
587 Self {
588 version: 450,
589 extensions: Vec::new(),
590 structs: Vec::new(),
591 uniforms: Vec::new(),
592 attributes: Vec::new(),
593 varyings: Vec::new(),
594 functions: Vec::new(),
595 global_vars: Vec::new(),
596 defines: HashMap::new(),
597 }
598 }
599 pub fn find_function(&self, name: &str) -> Option<&FunctionDecl> {
600 self.functions.iter().find(|f| f.name == name)
601 }
602 pub fn find_uniform(&self, name: &str) -> Option<&UniformDecl> {
603 self.uniforms.iter().find(|u| u.name == name)
604 }
605}
606
607pub struct Parser {
612 pub tokens: Vec<Token>,
613 pub pos: usize,
614 pub errors: Vec<ParseError>,
615}
616
617#[derive(Debug, Clone)]
618pub struct ParseError {
619 pub message: String,
620 pub line: u32,
621 pub col: u32,
622}
623
624impl Parser {
625 pub fn new(tokens: Vec<Token>) -> Self {
626 Self { tokens, pos: 0, errors: Vec::new() }
627 }
628 fn peek(&self) -> &Token { &self.tokens[self.pos.min(self.tokens.len()-1)] }
629 fn peek2(&self) -> &Token { &self.tokens[(self.pos+1).min(self.tokens.len()-1)] }
630 fn advance(&mut self) -> &Token {
631 let t = &self.tokens[self.pos.min(self.tokens.len()-1)];
632 if self.pos < self.tokens.len()-1 { self.pos += 1; }
633 t
634 }
635 fn expect(&mut self, kind: &TokenKind) -> bool {
636 if &self.peek().kind == kind { self.advance(); true }
637 else {
638 let tok = self.peek().clone();
639 self.errors.push(ParseError {
640 message: format!("Expected {:?}, got {:?}", kind, tok.kind),
641 line: tok.line, col: tok.col,
642 });
643 false
644 }
645 }
646 fn at_eof(&self) -> bool { matches!(self.peek().kind, TokenKind::Eof) }
647
648 fn parse_type(&mut self) -> ShaderType {
649 let tok = self.peek().kind.clone();
650 if let Some(ty) = ShaderType::from_token(&tok) {
651 self.advance();
652 return ty;
653 }
654 if let TokenKind::Ident(name) = &tok {
655 let name = name.clone();
656 self.advance();
657 return ShaderType::Struct(name);
658 }
659 ShaderType::Unknown
660 }
661
662 fn parse_expr_primary(&mut self) -> Expr {
663 match self.peek().kind.clone() {
664 TokenKind::IntLit(v) => { self.advance(); Expr::IntLit(v) }
665 TokenKind::FloatLit(v) => { self.advance(); Expr::FloatLit(v) }
666 TokenKind::BoolLit(v) => { self.advance(); Expr::BoolLit(v) }
667 TokenKind::Ident(name) => {
668 self.advance();
669 if self.peek().kind == TokenKind::LParen {
670 self.advance();
672 let mut args = Vec::new();
673 while self.peek().kind != TokenKind::RParen && !self.at_eof() {
674 args.push(self.parse_expr());
675 if self.peek().kind == TokenKind::Comma { self.advance(); }
676 else { break; }
677 }
678 self.expect(&TokenKind::RParen);
679 Expr::Call { function: name, args }
680 } else {
681 Expr::Ident(name)
682 }
683 }
684 TokenKind::LParen => {
685 self.advance();
686 let e = self.parse_expr();
687 self.expect(&TokenKind::RParen);
688 e
689 }
690 tok if ShaderType::from_token(&tok).is_some() => {
691 let ty = self.parse_type();
692 self.expect(&TokenKind::LParen);
693 let mut args = Vec::new();
694 while self.peek().kind != TokenKind::RParen && !self.at_eof() {
695 args.push(self.parse_expr());
696 if self.peek().kind == TokenKind::Comma { self.advance(); }
697 else { break; }
698 }
699 self.expect(&TokenKind::RParen);
700 Expr::Construction { ty, args }
701 }
702 _ => {
703 let tok = self.peek().clone();
704 self.errors.push(ParseError { message: format!("Unexpected token {:?}", tok.kind), line: tok.line, col: tok.col });
705 self.advance();
706 Expr::IntLit(0)
707 }
708 }
709 }
710
711 fn parse_expr_postfix(&mut self) -> Expr {
712 let mut e = self.parse_expr_primary();
713 loop {
714 match self.peek().kind.clone() {
715 TokenKind::Dot => {
716 self.advance();
717 if let TokenKind::Ident(field) = self.peek().kind.clone() {
718 self.advance();
719 e = Expr::Field { object: Box::new(e), field };
720 }
721 }
722 TokenKind::LBracket => {
723 self.advance();
724 let idx = self.parse_expr();
725 self.expect(&TokenKind::RBracket);
726 e = Expr::Index { array: Box::new(e), index: Box::new(idx) };
727 }
728 TokenKind::PlusPlus => { self.advance(); e = Expr::Unary { op: UnaryOp::PostIncrement, operand: Box::new(e) }; }
729 TokenKind::MinusMinus => { self.advance(); e = Expr::Unary { op: UnaryOp::PostDecrement, operand: Box::new(e) }; }
730 _ => break,
731 }
732 }
733 e
734 }
735
736 fn parse_expr_unary(&mut self) -> Expr {
737 match self.peek().kind {
738 TokenKind::Minus => { self.advance(); Expr::Unary { op: UnaryOp::Negate, operand: Box::new(self.parse_expr_unary()) } }
739 TokenKind::Bang => { self.advance(); Expr::Unary { op: UnaryOp::Not, operand: Box::new(self.parse_expr_unary()) } }
740 TokenKind::Tilde => { self.advance(); Expr::Unary { op: UnaryOp::BitNot, operand: Box::new(self.parse_expr_unary()) } }
741 TokenKind::PlusPlus => { self.advance(); Expr::Unary { op: UnaryOp::PreIncrement, operand: Box::new(self.parse_expr_unary()) } }
742 TokenKind::MinusMinus => { self.advance(); Expr::Unary { op: UnaryOp::PreDecrement, operand: Box::new(self.parse_expr_unary()) } }
743 _ => self.parse_expr_postfix(),
744 }
745 }
746
747 fn parse_expr_mul(&mut self) -> Expr {
748 let mut left = self.parse_expr_unary();
749 loop {
750 let op = match self.peek().kind {
751 TokenKind::Star => BinaryOp::Mul,
752 TokenKind::Slash => BinaryOp::Div,
753 TokenKind::Percent => BinaryOp::Mod,
754 _ => break,
755 };
756 self.advance();
757 let right = self.parse_expr_unary();
758 left = Expr::Binary { op, left: Box::new(left), right: Box::new(right) };
759 }
760 left
761 }
762
763 fn parse_expr_add(&mut self) -> Expr {
764 let mut left = self.parse_expr_mul();
765 loop {
766 let op = match self.peek().kind {
767 TokenKind::Plus => BinaryOp::Add,
768 TokenKind::Minus => BinaryOp::Sub,
769 _ => break,
770 };
771 self.advance();
772 let right = self.parse_expr_mul();
773 left = Expr::Binary { op, left: Box::new(left), right: Box::new(right) };
774 }
775 left
776 }
777
778 fn parse_expr_shift(&mut self) -> Expr {
779 let mut left = self.parse_expr_add();
780 loop {
781 let op = match self.peek().kind {
782 TokenKind::LShift => BinaryOp::Shl,
783 TokenKind::RShift => BinaryOp::Shr,
784 _ => break,
785 };
786 self.advance();
787 let right = self.parse_expr_add();
788 left = Expr::Binary { op, left: Box::new(left), right: Box::new(right) };
789 }
790 left
791 }
792
793 fn parse_expr_compare(&mut self) -> Expr {
794 let mut left = self.parse_expr_shift();
795 loop {
796 let op = match self.peek().kind {
797 TokenKind::Lt => BinaryOp::Lt,
798 TokenKind::Gt => BinaryOp::Gt,
799 TokenKind::LtEq => BinaryOp::Le,
800 TokenKind::GtEq => BinaryOp::Ge,
801 _ => break,
802 };
803 self.advance();
804 let right = self.parse_expr_shift();
805 left = Expr::Binary { op, left: Box::new(left), right: Box::new(right) };
806 }
807 left
808 }
809
810 fn parse_expr_equality(&mut self) -> Expr {
811 let mut left = self.parse_expr_compare();
812 loop {
813 let op = match self.peek().kind {
814 TokenKind::EqEq => BinaryOp::Eq,
815 TokenKind::BangEq => BinaryOp::Ne,
816 _ => break,
817 };
818 self.advance();
819 let right = self.parse_expr_compare();
820 left = Expr::Binary { op, left: Box::new(left), right: Box::new(right) };
821 }
822 left
823 }
824
825 fn parse_expr_bitand(&mut self) -> Expr {
826 let mut left = self.parse_expr_equality();
827 while self.peek().kind == TokenKind::Amp {
828 self.advance();
829 let right = self.parse_expr_equality();
830 left = Expr::Binary { op: BinaryOp::BitAnd, left: Box::new(left), right: Box::new(right) };
831 }
832 left
833 }
834
835 fn parse_expr_bitor(&mut self) -> Expr {
836 let mut left = self.parse_expr_bitand();
837 while self.peek().kind == TokenKind::Pipe {
838 self.advance();
839 let right = self.parse_expr_bitand();
840 left = Expr::Binary { op: BinaryOp::BitOr, left: Box::new(left), right: Box::new(right) };
841 }
842 left
843 }
844
845 fn parse_expr_and(&mut self) -> Expr {
846 let mut left = self.parse_expr_bitor();
847 while self.peek().kind == TokenKind::AmpAmp {
848 self.advance();
849 let right = self.parse_expr_bitor();
850 left = Expr::Binary { op: BinaryOp::And, left: Box::new(left), right: Box::new(right) };
851 }
852 left
853 }
854
855 fn parse_expr_or(&mut self) -> Expr {
856 let mut left = self.parse_expr_and();
857 while self.peek().kind == TokenKind::PipePipe {
858 self.advance();
859 let right = self.parse_expr_and();
860 left = Expr::Binary { op: BinaryOp::Or, left: Box::new(left), right: Box::new(right) };
861 }
862 left
863 }
864
865 fn parse_expr_ternary(&mut self) -> Expr {
866 let cond = self.parse_expr_or();
867 if self.peek().kind == TokenKind::Question {
868 self.advance();
869 let then_expr = self.parse_expr();
870 self.expect(&TokenKind::Colon);
871 let else_expr = self.parse_expr_ternary();
872 Expr::Ternary { cond: Box::new(cond), then_expr: Box::new(then_expr), else_expr: Box::new(else_expr) }
873 } else { cond }
874 }
875
876 fn parse_expr_assign(&mut self) -> Expr {
877 let left = self.parse_expr_ternary();
878 let op = match self.peek().kind {
879 TokenKind::Eq => AssignOp::Assign,
880 TokenKind::PlusEq => AssignOp::AddAssign,
881 TokenKind::MinusEq => AssignOp::SubAssign,
882 TokenKind::StarEq => AssignOp::MulAssign,
883 TokenKind::SlashEq => AssignOp::DivAssign,
884 TokenKind::PercentEq => AssignOp::ModAssign,
885 TokenKind::AmpEq => AssignOp::AndAssign,
886 TokenKind::PipeEq => AssignOp::OrAssign,
887 TokenKind::CaretEq => AssignOp::XorAssign,
888 TokenKind::LShiftEq => AssignOp::ShlAssign,
889 TokenKind::RShiftEq => AssignOp::ShrAssign,
890 _ => return left,
891 };
892 self.advance();
893 let value = self.parse_expr_assign();
894 Expr::Assign { op, target: Box::new(left), value: Box::new(value) }
895 }
896
897 pub fn parse_expr(&mut self) -> Expr { self.parse_expr_assign() }
898
899 fn parse_stmt(&mut self) -> Option<Stmt> {
900 match self.peek().kind.clone() {
901 TokenKind::LBrace => {
902 self.advance();
903 let mut stmts = Vec::new();
904 while self.peek().kind != TokenKind::RBrace && !self.at_eof() {
905 if let Some(s) = self.parse_stmt() { stmts.push(s); }
906 }
907 self.expect(&TokenKind::RBrace);
908 Some(Stmt::Block(stmts))
909 }
910 TokenKind::KwIf => {
911 self.advance();
912 self.expect(&TokenKind::LParen);
913 let cond = self.parse_expr();
914 self.expect(&TokenKind::RParen);
915 let then_body = Box::new(self.parse_stmt()?);
916 let else_body = if self.peek().kind == TokenKind::KwElse {
917 self.advance();
918 Some(Box::new(self.parse_stmt()?))
919 } else { None };
920 Some(Stmt::If { cond, then_body, else_body })
921 }
922 TokenKind::KwFor => {
923 self.advance();
924 self.expect(&TokenKind::LParen);
925 let init = if self.peek().kind != TokenKind::Semicolon {
926 self.parse_stmt().map(Box::new)
927 } else { self.advance(); None };
928 let cond = if self.peek().kind != TokenKind::Semicolon {
929 Some(self.parse_expr())
930 } else { None };
931 self.expect(&TokenKind::Semicolon);
932 let step = if self.peek().kind != TokenKind::RParen {
933 Some(self.parse_expr())
934 } else { None };
935 self.expect(&TokenKind::RParen);
936 let body = Box::new(self.parse_stmt()?);
937 Some(Stmt::For { init, cond, step, body })
938 }
939 TokenKind::KwWhile => {
940 self.advance();
941 self.expect(&TokenKind::LParen);
942 let cond = self.parse_expr();
943 self.expect(&TokenKind::RParen);
944 let body = Box::new(self.parse_stmt()?);
945 Some(Stmt::While { cond, body })
946 }
947 TokenKind::KwDo => {
948 self.advance();
949 let body = Box::new(self.parse_stmt()?);
950 self.expect(&TokenKind::KwWhile);
951 self.expect(&TokenKind::LParen);
952 let cond = self.parse_expr();
953 self.expect(&TokenKind::RParen);
954 self.expect(&TokenKind::Semicolon);
955 Some(Stmt::DoWhile { body, cond })
956 }
957 TokenKind::KwReturn => {
958 self.advance();
959 let val = if self.peek().kind != TokenKind::Semicolon {
960 Some(self.parse_expr())
961 } else { None };
962 self.expect(&TokenKind::Semicolon);
963 Some(Stmt::Return(val))
964 }
965 TokenKind::KwBreak => { self.advance(); self.expect(&TokenKind::Semicolon); Some(Stmt::Break) }
966 TokenKind::KwContinue => { self.advance(); self.expect(&TokenKind::Semicolon); Some(Stmt::Continue) }
967 TokenKind::KwDiscard => { self.advance(); self.expect(&TokenKind::Semicolon); Some(Stmt::Discard) }
968 tok if ShaderType::from_token(&tok).is_some() || matches!(tok, TokenKind::Ident(_)) => {
969 let saved_pos = self.pos;
971 let ty = self.parse_type();
972 if let TokenKind::Ident(name) = self.peek().kind.clone() {
973 self.advance();
974 let init = if self.peek().kind == TokenKind::Eq {
975 self.advance();
976 Some(self.parse_expr())
977 } else { None };
978 self.expect(&TokenKind::Semicolon);
979 Some(Stmt::VarDecl { ty, name, qualifier: None, init })
980 } else {
981 self.pos = saved_pos;
983 let e = self.parse_expr();
984 self.expect(&TokenKind::Semicolon);
985 Some(Stmt::Expr(e))
986 }
987 }
988 _ => {
989 let e = self.parse_expr();
990 self.expect(&TokenKind::Semicolon);
991 Some(Stmt::Expr(e))
992 }
993 }
994 }
995
996 fn parse_function(&mut self, return_type: ShaderType, name: String) -> FunctionDecl {
997 self.expect(&TokenKind::LParen);
999 let mut params = Vec::new();
1000 while self.peek().kind != TokenKind::RParen && !self.at_eof() {
1001 let qualifier = match self.peek().kind {
1002 TokenKind::KwIn => { self.advance(); Some(VarQualifier::In) }
1003 TokenKind::KwOut => { self.advance(); Some(VarQualifier::Out) }
1004 TokenKind::KwInout => { self.advance(); Some(VarQualifier::Inout) }
1005 TokenKind::KwConst => { self.advance(); Some(VarQualifier::Const) }
1006 _ => None,
1007 };
1008 let ty = self.parse_type();
1009 let pname = if let TokenKind::Ident(n) = self.peek().kind.clone() { self.advance(); n } else { String::new() };
1010 params.push(FunctionParam { ty, name: pname, qualifier });
1011 if self.peek().kind == TokenKind::Comma { self.advance(); } else { break; }
1012 }
1013 self.expect(&TokenKind::RParen);
1014 let mut body = Vec::new();
1015 if self.peek().kind == TokenKind::LBrace {
1016 self.advance();
1017 while self.peek().kind != TokenKind::RBrace && !self.at_eof() {
1018 if let Some(s) = self.parse_stmt() { body.push(s); }
1019 }
1020 self.expect(&TokenKind::RBrace);
1021 } else {
1022 self.expect(&TokenKind::Semicolon);
1023 }
1024 FunctionDecl { return_type, name, params, body, is_builtin: false }
1025 }
1026
1027 pub fn parse_translation_unit(&mut self) -> ShaderAst {
1028 let mut ast = ShaderAst::new();
1029 while !self.at_eof() {
1030 if self.peek().kind == TokenKind::Hash {
1032 self.advance();
1033 if let TokenKind::Ident(directive) = self.peek().kind.clone() {
1034 self.advance();
1035 match directive.as_str() {
1036 "version" => {
1037 if let TokenKind::IntLit(v) = self.peek().kind { self.advance(); ast.version = v as u32; }
1038 }
1039 "define" => {
1040 if let TokenKind::Ident(macro_name) = self.peek().kind.clone() {
1041 self.advance();
1042 let val = if let TokenKind::Ident(v) = self.peek().kind.clone() { self.advance(); v } else { String::new() };
1043 ast.defines.insert(macro_name, val);
1044 }
1045 }
1046 _ => {}
1047 }
1048 }
1049 continue;
1050 }
1051 let mut binding = None;
1053 let mut set_num = None;
1054 if self.peek().kind == TokenKind::KwLayout {
1055 self.advance();
1056 self.expect(&TokenKind::LParen);
1057 while self.peek().kind != TokenKind::RParen && !self.at_eof() {
1058 if let TokenKind::Ident(k) = self.peek().kind.clone() {
1059 self.advance();
1060 if self.peek().kind == TokenKind::Eq {
1061 self.advance();
1062 if let TokenKind::IntLit(v) = self.peek().kind { self.advance();
1063 if k == "binding" { binding = Some(v as u32); }
1064 else if k == "set" { set_num = Some(v as u32); }
1065 }
1066 }
1067 } else { self.advance(); }
1068 if self.peek().kind == TokenKind::Comma { self.advance(); } else { break; }
1069 }
1070 self.expect(&TokenKind::RParen);
1071 }
1072 let qualifier = match self.peek().kind {
1074 TokenKind::KwUniform => { self.advance(); Some(VarQualifier::Uniform) }
1075 TokenKind::KwIn | TokenKind::KwAttribute => { self.advance(); Some(VarQualifier::In) }
1076 TokenKind::KwOut | TokenKind::KwVarying => { self.advance(); Some(VarQualifier::Out) }
1077 TokenKind::KwConst => { self.advance(); Some(VarQualifier::Const) }
1078 _ => None,
1079 };
1080 if matches!(self.peek().kind, TokenKind::KwPrecision|TokenKind::KwHighp|TokenKind::KwMediump|TokenKind::KwLowp) {
1082 self.advance();
1083 if matches!(self.peek().kind, TokenKind::KwHighp|TokenKind::KwMediump|TokenKind::KwLowp) { self.advance(); }
1084 self.parse_type();
1085 self.expect(&TokenKind::Semicolon);
1086 continue;
1087 }
1088 if self.peek().kind == TokenKind::KwStruct {
1090 self.advance();
1091 let sname = if let TokenKind::Ident(n) = self.peek().kind.clone() { self.advance(); n } else { String::new() };
1092 let mut fields = Vec::new();
1093 self.expect(&TokenKind::LBrace);
1094 while self.peek().kind != TokenKind::RBrace && !self.at_eof() {
1095 let fty = self.parse_type();
1096 while let TokenKind::Ident(fname) = self.peek().kind.clone() {
1097 self.advance();
1098 let arr = if self.peek().kind == TokenKind::LBracket {
1099 self.advance();
1100 let sz = if let TokenKind::IntLit(v) = self.peek().kind { self.advance(); Some(v as u32) } else { None };
1101 self.expect(&TokenKind::RBracket);
1102 sz
1103 } else { None };
1104 fields.push(StructField { ty: fty.clone(), name: fname, array_size: arr });
1105 if self.peek().kind == TokenKind::Comma { self.advance(); } else { break; }
1106 }
1107 self.expect(&TokenKind::Semicolon);
1108 }
1109 self.expect(&TokenKind::RBrace);
1110 self.expect(&TokenKind::Semicolon);
1111 ast.structs.push(StructDecl { name: sname, fields });
1112 continue;
1113 }
1114 if self.peek().is_type_keyword() || matches!(self.peek().kind, TokenKind::Ident(_)) {
1116 let ty = self.parse_type();
1117 let name = if let TokenKind::Ident(n) = self.peek().kind.clone() { self.advance(); n } else { String::new() };
1118 let arr_size = if self.peek().kind == TokenKind::LBracket {
1120 self.advance();
1121 let sz = if let TokenKind::IntLit(v) = self.peek().kind { self.advance(); Some(v as u32) } else { None };
1122 self.expect(&TokenKind::RBracket);
1123 sz
1124 } else { None };
1125 if self.peek().kind == TokenKind::LParen {
1127 let func = self.parse_function(ty, name);
1128 ast.functions.push(func);
1129 } else {
1130 match qualifier {
1132 Some(VarQualifier::Uniform) => {
1133 ast.uniforms.push(UniformDecl { ty, name, binding, set: set_num, array_size: arr_size });
1134 self.expect(&TokenKind::Semicolon);
1135 }
1136 Some(VarQualifier::In) => {
1137 ast.attributes.push(AttributeDecl { ty, name, location: binding });
1138 self.expect(&TokenKind::Semicolon);
1139 }
1140 Some(VarQualifier::Out) => {
1141 ast.varyings.push(AttributeDecl { ty, name, location: binding });
1142 self.expect(&TokenKind::Semicolon);
1143 }
1144 _ => {
1145 let init = if self.peek().kind == TokenKind::Eq { self.advance(); Some(self.parse_expr()) } else { None };
1146 self.expect(&TokenKind::Semicolon);
1147 ast.global_vars.push(Stmt::VarDecl { ty, name, qualifier, init });
1148 }
1149 }
1150 }
1151 } else {
1152 self.advance(); }
1154 }
1155 ast
1156 }
1157}
1158
1159#[derive(Debug, Clone)]
1164pub struct Symbol {
1165 pub name: String,
1166 pub ty: ShaderType,
1167 pub qualifier: Option<VarQualifier>,
1168 pub used: bool,
1169 pub defined_at_line: u32,
1170}
1171
1172pub struct Scope {
1173 pub symbols: HashMap<String, Symbol>,
1174 pub parent: Option<*mut Scope>,
1175}
1176
1177impl Scope {
1178 pub fn new() -> Self { Self { symbols: HashMap::new(), parent: None } }
1179 pub fn lookup(&self, name: &str) -> Option<&Symbol> {
1180 if let Some(s) = self.symbols.get(name) { return Some(s); }
1181 None
1182 }
1183 pub fn insert(&mut self, sym: Symbol) { self.symbols.insert(sym.name.clone(), sym); }
1184}
1185
1186#[derive(Debug, Clone)]
1187pub struct SemanticError {
1188 pub kind: SemanticErrorKind,
1189 pub message: String,
1190 pub line: u32,
1191}
1192
1193#[derive(Debug, Clone)]
1194pub enum SemanticErrorKind {
1195 UndeclaredVariable,
1196 TypeMismatch,
1197 RedefinedSymbol,
1198 InvalidOperation,
1199 UnusedVariable,
1200 RecursionDetected,
1201 ReturnTypeMismatch,
1202 InvalidArgCount,
1203}
1204
1205pub struct SemanticAnalyzer {
1206 pub scopes: Vec<HashMap<String, Symbol>>,
1207 pub errors: Vec<SemanticError>,
1208 pub warnings: Vec<SemanticError>,
1209 pub current_function: Option<String>,
1210 pub call_stack: HashSet<String>,
1211 pub built_in_functions: HashMap<String, BuiltinSignature>,
1212}
1213
1214#[derive(Debug, Clone)]
1215pub struct BuiltinSignature {
1216 pub name: String,
1217 pub overloads: Vec<(Vec<ShaderType>, ShaderType)>, }
1219
1220impl SemanticAnalyzer {
1221 pub fn new() -> Self {
1222 let mut a = Self {
1223 scopes: vec![HashMap::new()],
1224 errors: Vec::new(),
1225 warnings: Vec::new(),
1226 current_function: None,
1227 call_stack: HashSet::new(),
1228 built_in_functions: HashMap::new(),
1229 };
1230 a.register_builtins();
1231 a
1232 }
1233
1234 fn register_builtins(&mut self) {
1235 let math_float_vec = |name: &str| {
1236 let overloads = vec![
1237 (vec![ShaderType::Float], ShaderType::Float),
1238 (vec![ShaderType::Vec2], ShaderType::Vec2),
1239 (vec![ShaderType::Vec3], ShaderType::Vec3),
1240 (vec![ShaderType::Vec4], ShaderType::Vec4),
1241 ];
1242 BuiltinSignature { name: name.to_string(), overloads }
1243 };
1244 let names = &["sin","cos","tan","asin","acos","atan","sinh","cosh","tanh","exp","exp2","log","log2","sqrt","inversesqrt","abs","sign","floor","ceil","round","fract","normalize","length","radians","degrees"];
1245 for n in names {
1246 self.built_in_functions.insert(n.to_string(), math_float_vec(n));
1247 }
1248 self.built_in_functions.insert("pow".to_string(), BuiltinSignature { name: "pow".to_string(), overloads: vec![(vec![ShaderType::Float,ShaderType::Float],ShaderType::Float),(vec![ShaderType::Vec2,ShaderType::Vec2],ShaderType::Vec2),(vec![ShaderType::Vec3,ShaderType::Vec3],ShaderType::Vec3),(vec![ShaderType::Vec4,ShaderType::Vec4],ShaderType::Vec4)] });
1249 self.built_in_functions.insert("mix".to_string(), BuiltinSignature { name: "mix".to_string(), overloads: vec![(vec![ShaderType::Float,ShaderType::Float,ShaderType::Float],ShaderType::Float),(vec![ShaderType::Vec2,ShaderType::Vec2,ShaderType::Float],ShaderType::Vec2),(vec![ShaderType::Vec3,ShaderType::Vec3,ShaderType::Float],ShaderType::Vec3)] });
1250 self.built_in_functions.insert("clamp".to_string(), BuiltinSignature { name: "clamp".to_string(), overloads: vec![(vec![ShaderType::Float,ShaderType::Float,ShaderType::Float],ShaderType::Float),(vec![ShaderType::Vec2,ShaderType::Float,ShaderType::Float],ShaderType::Vec2)] });
1251 self.built_in_functions.insert("smoothstep".to_string(), BuiltinSignature { name: "smoothstep".to_string(), overloads: vec![(vec![ShaderType::Float,ShaderType::Float,ShaderType::Float],ShaderType::Float)] });
1252 self.built_in_functions.insert("dot".to_string(), BuiltinSignature { name: "dot".to_string(), overloads: vec![(vec![ShaderType::Vec2,ShaderType::Vec2],ShaderType::Float),(vec![ShaderType::Vec3,ShaderType::Vec3],ShaderType::Float),(vec![ShaderType::Vec4,ShaderType::Vec4],ShaderType::Float)] });
1253 self.built_in_functions.insert("cross".to_string(), BuiltinSignature { name: "cross".to_string(), overloads: vec![(vec![ShaderType::Vec3,ShaderType::Vec3],ShaderType::Vec3)] });
1254 self.built_in_functions.insert("reflect".to_string(), BuiltinSignature { name: "reflect".to_string(), overloads: vec![(vec![ShaderType::Vec3,ShaderType::Vec3],ShaderType::Vec3)] });
1255 self.built_in_functions.insert("refract".to_string(), BuiltinSignature { name: "refract".to_string(), overloads: vec![(vec![ShaderType::Vec3,ShaderType::Vec3,ShaderType::Float],ShaderType::Vec3)] });
1256 self.built_in_functions.insert("max".to_string(), BuiltinSignature { name: "max".to_string(), overloads: vec![(vec![ShaderType::Float,ShaderType::Float],ShaderType::Float)] });
1257 self.built_in_functions.insert("min".to_string(), BuiltinSignature { name: "min".to_string(), overloads: vec![(vec![ShaderType::Float,ShaderType::Float],ShaderType::Float)] });
1258 self.built_in_functions.insert("mod".to_string(), BuiltinSignature { name: "mod".to_string(), overloads: vec![(vec![ShaderType::Float,ShaderType::Float],ShaderType::Float),(vec![ShaderType::Vec2,ShaderType::Float],ShaderType::Vec2)] });
1259 self.built_in_functions.insert("texture".to_string(), BuiltinSignature { name: "texture".to_string(), overloads: vec![(vec![ShaderType::Sampler2D,ShaderType::Vec2],ShaderType::Vec4),(vec![ShaderType::SamplerCube,ShaderType::Vec3],ShaderType::Vec4)] });
1260 self.built_in_functions.insert("textureLod".to_string(), BuiltinSignature { name: "textureLod".to_string(), overloads: vec![(vec![ShaderType::Sampler2D,ShaderType::Vec2,ShaderType::Float],ShaderType::Vec4)] });
1261 self.built_in_functions.insert("dFdx".to_string(), BuiltinSignature { name: "dFdx".to_string(), overloads: vec![(vec![ShaderType::Float],ShaderType::Float),(vec![ShaderType::Vec2],ShaderType::Vec2),(vec![ShaderType::Vec3],ShaderType::Vec3)] });
1262 self.built_in_functions.insert("dFdy".to_string(), BuiltinSignature { name: "dFdy".to_string(), overloads: vec![(vec![ShaderType::Float],ShaderType::Float),(vec![ShaderType::Vec2],ShaderType::Vec2),(vec![ShaderType::Vec3],ShaderType::Vec3)] });
1263 }
1264
1265 fn push_scope(&mut self) { self.scopes.push(HashMap::new()); }
1266 fn pop_scope(&mut self) { self.scopes.pop(); }
1267 fn current_scope(&mut self) -> &mut HashMap<String, Symbol> { self.scopes.last_mut().unwrap() }
1268
1269 fn declare(&mut self, sym: Symbol) {
1270 let name = sym.name.clone();
1271 if self.current_scope().contains_key(&name) {
1272 self.errors.push(SemanticError { kind: SemanticErrorKind::RedefinedSymbol, message: format!("Redefined symbol '{}'", name), line: sym.defined_at_line });
1273 }
1274 self.current_scope().insert(name, sym);
1275 }
1276
1277 fn lookup(&self, name: &str) -> Option<&Symbol> {
1278 for scope in self.scopes.iter().rev() {
1279 if let Some(s) = scope.get(name) { return Some(s); }
1280 }
1281 None
1282 }
1283
1284 fn infer_type(&self, expr: &Expr) -> ShaderType {
1285 match expr {
1286 Expr::IntLit(_) => ShaderType::Int,
1287 Expr::FloatLit(_) => ShaderType::Float,
1288 Expr::BoolLit(_) => ShaderType::Bool,
1289 Expr::Ident(name) => {
1290 if let Some(sym) = self.lookup(name) { sym.ty.clone() } else { ShaderType::Unknown }
1291 }
1292 Expr::Binary { op, left, right } => {
1293 let lt = self.infer_type(left);
1294 let rt = self.infer_type(right);
1295 match op {
1296 BinaryOp::Eq|BinaryOp::Ne|BinaryOp::Lt|BinaryOp::Gt|BinaryOp::Le|BinaryOp::Ge|BinaryOp::And|BinaryOp::Or => ShaderType::Bool,
1297 _ => if lt == rt { lt } else if ShaderType::can_implicit_convert(&rt, <) { lt } else { lt }
1298 }
1299 }
1300 Expr::Unary { op, operand } => self.infer_type(operand),
1301 Expr::Call { function, args } => {
1302 if let Some(sig) = self.built_in_functions.get(function) {
1303 let arg_types: Vec<ShaderType> = args.iter().map(|a| self.infer_type(a)).collect();
1304 for (params, ret) in &sig.overloads {
1305 if params.len() == arg_types.len() { return ret.clone(); }
1306 }
1307 }
1308 ShaderType::Unknown
1309 }
1310 Expr::Field { object, field } => ShaderType::Float, Expr::Swizzle { object, components } => {
1312 let n = components.len();
1313 match n {
1314 1 => ShaderType::Float,
1315 2 => ShaderType::Vec2,
1316 3 => ShaderType::Vec3,
1317 4 => ShaderType::Vec4,
1318 _ => ShaderType::Unknown,
1319 }
1320 }
1321 Expr::Index { array, .. } => {
1322 match self.infer_type(array) {
1323 ShaderType::Array(inner, _) => *inner,
1324 ShaderType::Vec2|ShaderType::Vec3|ShaderType::Vec4 => ShaderType::Float,
1325 ShaderType::Mat2|ShaderType::Mat3|ShaderType::Mat4 => ShaderType::Vec4, other => other,
1327 }
1328 }
1329 Expr::Construction { ty, .. } => ty.clone(),
1330 Expr::Cast { target_type, .. } => target_type.clone(),
1331 Expr::Assign { value, .. } => self.infer_type(value),
1332 Expr::Ternary { then_expr, .. } => self.infer_type(then_expr),
1333 }
1334 }
1335
1336 fn check_expr(&mut self, expr: &Expr) {
1337 match expr {
1338 Expr::Ident(name) => {
1339 if self.lookup(name).is_none() && !self.built_in_functions.contains_key(name.as_str()) {
1340 let known = ["gl_Position","gl_FragCoord","gl_FragDepth","gl_VertexID","gl_InstanceID","true","false","PI","E"];
1342 if !known.contains(&name.as_str()) {
1343 self.errors.push(SemanticError { kind: SemanticErrorKind::UndeclaredVariable, message: format!("Undeclared variable '{}'", name), line: 0 });
1344 }
1345 }
1346 }
1347 Expr::Binary { op, left, right } => {
1348 self.check_expr(left);
1349 self.check_expr(right);
1350 let lt = self.infer_type(left);
1351 let rt = self.infer_type(right);
1352 if lt != rt && !ShaderType::can_implicit_convert(<, &rt) && !ShaderType::can_implicit_convert(&rt, <) && lt != ShaderType::Unknown && rt != ShaderType::Unknown {
1353 }
1355 }
1356 Expr::Call { function, args } => {
1357 for a in args { self.check_expr(a); }
1358 if !self.built_in_functions.contains_key(function.as_str()) {
1359 if let Some(fname) = &self.current_function {
1361 if fname == function {
1362 self.errors.push(SemanticError { kind: SemanticErrorKind::RecursionDetected, message: format!("Recursive call to '{}'", function), line: 0 });
1363 }
1364 }
1365 }
1366 }
1367 Expr::Assign { target, value, .. } => {
1368 self.check_expr(target);
1369 self.check_expr(value);
1370 }
1371 Expr::Unary { operand, .. } => self.check_expr(operand),
1372 Expr::Ternary { cond, then_expr, else_expr } => {
1373 self.check_expr(cond);
1374 self.check_expr(then_expr);
1375 self.check_expr(else_expr);
1376 }
1377 Expr::Field { object, .. } | Expr::Swizzle { object, .. } => self.check_expr(object),
1378 Expr::Index { array, index } => { self.check_expr(array); self.check_expr(index); }
1379 Expr::Construction { args, .. } => { for a in args { self.check_expr(a); } }
1380 _ => {}
1381 }
1382 }
1383
1384 fn check_stmt(&mut self, stmt: &Stmt) {
1385 match stmt {
1386 Stmt::Expr(e) => self.check_expr(e),
1387 Stmt::Block(stmts) => {
1388 self.push_scope();
1389 for s in stmts { self.check_stmt(s); }
1390 self.pop_scope();
1391 }
1392 Stmt::VarDecl { ty, name, qualifier, init } => {
1393 if let Some(init_expr) = init { self.check_expr(init_expr); }
1394 self.declare(Symbol { name: name.clone(), ty: ty.clone(), qualifier: *qualifier, used: false, defined_at_line: 0 });
1395 }
1396 Stmt::If { cond, then_body, else_body } => {
1397 self.check_expr(cond);
1398 self.check_stmt(then_body);
1399 if let Some(e) = else_body { self.check_stmt(e); }
1400 }
1401 Stmt::For { init, cond, step, body } => {
1402 self.push_scope();
1403 if let Some(i) = init { self.check_stmt(i); }
1404 if let Some(c) = cond { self.check_expr(c); }
1405 if let Some(s) = step { self.check_expr(s); }
1406 self.check_stmt(body);
1407 self.pop_scope();
1408 }
1409 Stmt::While { cond, body } => { self.check_expr(cond); self.check_stmt(body); }
1410 Stmt::DoWhile { body, cond } => { self.check_stmt(body); self.check_expr(cond); }
1411 Stmt::Return(val) => { if let Some(v) = val { self.check_expr(v); } }
1412 _ => {}
1413 }
1414 }
1415
1416 pub fn analyze(&mut self, ast: &ShaderAst) {
1417 for u in &ast.uniforms {
1419 self.declare(Symbol { name: u.name.clone(), ty: u.ty.clone(), qualifier: Some(VarQualifier::Uniform), used: false, defined_at_line: 0 });
1420 }
1421 for a in &ast.attributes {
1422 self.declare(Symbol { name: a.name.clone(), ty: a.ty.clone(), qualifier: Some(VarQualifier::In), used: false, defined_at_line: 0 });
1423 }
1424 for v in &ast.varyings {
1425 self.declare(Symbol { name: v.name.clone(), ty: v.ty.clone(), qualifier: Some(VarQualifier::Out), used: false, defined_at_line: 0 });
1426 }
1427 for s in &ast.structs {
1429 self.declare(Symbol { name: s.name.clone(), ty: ShaderType::Struct(s.name.clone()), qualifier: None, used: false, defined_at_line: 0 });
1430 }
1431 for func in &ast.functions {
1433 self.current_function = Some(func.name.clone());
1434 self.push_scope();
1435 for param in &func.params {
1436 self.declare(Symbol { name: param.name.clone(), ty: param.ty.clone(), qualifier: param.qualifier, used: false, defined_at_line: 0 });
1437 }
1438 for stmt in &func.body { self.check_stmt(stmt); }
1439 self.pop_scope();
1440 }
1441 self.current_function = None;
1442 }
1443}
1444
1445pub fn fold_expr(expr: Expr) -> Expr {
1450 match expr {
1451 Expr::Binary { op, left, right } => {
1452 let left = fold_expr(*left);
1453 let right = fold_expr(*right);
1454 match (&left, &right) {
1455 (Expr::FloatLit(a), Expr::FloatLit(b)) => {
1456 match op {
1457 BinaryOp::Add => Expr::FloatLit(a + b),
1458 BinaryOp::Sub => Expr::FloatLit(a - b),
1459 BinaryOp::Mul => Expr::FloatLit(a * b),
1460 BinaryOp::Div => if *b != 0.0 { Expr::FloatLit(a / b) } else { Expr::Binary { op, left: Box::new(left), right: Box::new(right) } },
1461 BinaryOp::Eq => Expr::BoolLit(a == b),
1462 BinaryOp::Ne => Expr::BoolLit(a != b),
1463 BinaryOp::Lt => Expr::BoolLit(a < b),
1464 BinaryOp::Gt => Expr::BoolLit(a > b),
1465 BinaryOp::Le => Expr::BoolLit(a <= b),
1466 BinaryOp::Ge => Expr::BoolLit(a >= b),
1467 _ => Expr::Binary { op, left: Box::new(left), right: Box::new(right) },
1468 }
1469 }
1470 (Expr::IntLit(a), Expr::IntLit(b)) => {
1471 match op {
1472 BinaryOp::Add => Expr::IntLit(a + b),
1473 BinaryOp::Sub => Expr::IntLit(a - b),
1474 BinaryOp::Mul => Expr::IntLit(a * b),
1475 BinaryOp::Div => if *b != 0 { Expr::IntLit(a / b) } else { Expr::Binary { op, left: Box::new(left), right: Box::new(right) } },
1476 BinaryOp::Mod => if *b != 0 { Expr::IntLit(a % b) } else { Expr::Binary { op, left: Box::new(left), right: Box::new(right) } },
1477 BinaryOp::BitAnd => Expr::IntLit(a & b),
1478 BinaryOp::BitOr => Expr::IntLit(a | b),
1479 BinaryOp::BitXor => Expr::IntLit(a ^ b),
1480 BinaryOp::Shl => Expr::IntLit(a << (b & 63)),
1481 BinaryOp::Shr => Expr::IntLit(a >> (b & 63)),
1482 BinaryOp::Eq => Expr::BoolLit(a == b),
1483 BinaryOp::Ne => Expr::BoolLit(a != b),
1484 BinaryOp::Lt => Expr::BoolLit(a < b),
1485 BinaryOp::Gt => Expr::BoolLit(a > b),
1486 BinaryOp::Le => Expr::BoolLit(a <= b),
1487 BinaryOp::Ge => Expr::BoolLit(a >= b),
1488 _ => Expr::Binary { op, left: Box::new(left), right: Box::new(right) },
1489 }
1490 }
1491 (Expr::BoolLit(a), Expr::BoolLit(b)) => {
1492 match op {
1493 BinaryOp::And => Expr::BoolLit(*a && *b),
1494 BinaryOp::Or => Expr::BoolLit(*a || *b),
1495 BinaryOp::Eq => Expr::BoolLit(a == b),
1496 BinaryOp::Ne => Expr::BoolLit(a != b),
1497 _ => Expr::Binary { op, left: Box::new(left), right: Box::new(right) },
1498 }
1499 }
1500 (_, Expr::FloatLit(v)) if op == BinaryOp::Mul && *v == 1.0 => left,
1502 (Expr::FloatLit(v), _) if op == BinaryOp::Mul && *v == 1.0 => right,
1503 (_, Expr::FloatLit(v)) if op == BinaryOp::Mul && *v == 0.0 => Expr::FloatLit(0.0),
1504 (Expr::FloatLit(v), _) if op == BinaryOp::Mul && *v == 0.0 => Expr::FloatLit(0.0),
1505 (_, Expr::FloatLit(v)) if op == BinaryOp::Add && *v == 0.0 => left,
1506 (Expr::FloatLit(v), _) if op == BinaryOp::Add && *v == 0.0 => right,
1507 (_, Expr::FloatLit(v)) if op == BinaryOp::Sub && *v == 0.0 => left,
1508 (_, Expr::FloatLit(v)) if op == BinaryOp::Div && *v == 1.0 => left,
1509 (_, Expr::IntLit(v)) if op == BinaryOp::Mul && *v == 1 => left,
1510 (Expr::IntLit(v), _) if op == BinaryOp::Mul && *v == 1 => right,
1511 (_, Expr::IntLit(v)) if op == BinaryOp::Add && *v == 0 => left,
1512 (Expr::IntLit(v), _) if op == BinaryOp::Add && *v == 0 => right,
1513 _ => Expr::Binary { op, left: Box::new(left), right: Box::new(right) },
1514 }
1515 }
1516 Expr::Unary { op, operand } => {
1517 let operand = fold_expr(*operand);
1518 match (&operand, op) {
1519 (Expr::FloatLit(v), UnaryOp::Negate) => Expr::FloatLit(-v),
1520 (Expr::IntLit(v), UnaryOp::Negate) => Expr::IntLit(-v),
1521 (Expr::BoolLit(v), UnaryOp::Not) => Expr::BoolLit(!v),
1522 (Expr::IntLit(v), UnaryOp::BitNot) => Expr::IntLit(!v),
1523 _ => Expr::Unary { op, operand: Box::new(operand) },
1524 }
1525 }
1526 Expr::Ternary { cond, then_expr, else_expr } => {
1527 let cond = fold_expr(*cond);
1528 let then_expr = fold_expr(*then_expr);
1529 let else_expr = fold_expr(*else_expr);
1530 match &cond {
1531 Expr::BoolLit(true) => then_expr,
1532 Expr::BoolLit(false) => else_expr,
1533 _ => Expr::Ternary { cond: Box::new(cond), then_expr: Box::new(then_expr), else_expr: Box::new(else_expr) },
1534 }
1535 }
1536 Expr::Call { function, args } => {
1537 let args: Vec<Expr> = args.into_iter().map(fold_expr).collect();
1538 let all_float_lits: Vec<f64> = args.iter().filter_map(|a| if let Expr::FloatLit(v) = a { Some(*v) } else { None }).collect();
1540 if all_float_lits.len() == args.len() {
1541 let result = fold_builtin_call(&function, &all_float_lits);
1542 if let Some(v) = result { return Expr::FloatLit(v); }
1543 }
1544 Expr::Call { function, args }
1545 }
1546 other => other,
1547 }
1548}
1549
1550fn fold_builtin_call(name: &str, args: &[f64]) -> Option<f64> {
1551 match (name, args) {
1552 ("sin", [x]) => Some(x.sin()),
1553 ("cos", [x]) => Some(x.cos()),
1554 ("tan", [x]) => Some(x.tan()),
1555 ("asin", [x]) => Some(x.asin()),
1556 ("acos", [x]) => Some(x.acos()),
1557 ("atan", [x]) => Some(x.atan()),
1558 ("sqrt", [x]) if *x >= 0.0 => Some(x.sqrt()),
1559 ("inversesqrt", [x]) if *x > 0.0 => Some(1.0 / x.sqrt()),
1560 ("exp", [x]) => Some(x.exp()),
1561 ("exp2", [x]) => Some(x.exp2()),
1562 ("log", [x]) if *x > 0.0 => Some(x.ln()),
1563 ("log2", [x]) if *x > 0.0 => Some(x.log2()),
1564 ("abs", [x]) => Some(x.abs()),
1565 ("sign", [x]) => Some(x.signum()),
1566 ("floor", [x]) => Some(x.floor()),
1567 ("ceil", [x]) => Some(x.ceil()),
1568 ("round", [x]) => Some(x.round()),
1569 ("fract", [x]) => Some(x.fract()),
1570 ("pow", [x, y]) => Some(x.powf(*y)),
1571 ("min", [a, b]) => Some(a.min(*b)),
1572 ("max", [a, b]) => Some(a.max(*b)),
1573 ("clamp", [x, mn, mx]) => Some(x.clamp(*mn, *mx)),
1574 ("mix", [a, b, t]) => Some(a + (b - a) * t),
1575 ("smoothstep", [e0, e1, x]) => {
1576 let t = ((x - e0) / (e1 - e0)).clamp(0.0, 1.0);
1577 Some(t * t * (3.0 - 2.0 * t))
1578 }
1579 ("radians", [x]) => Some(x.to_radians()),
1580 ("degrees", [x]) => Some(x.to_degrees()),
1581 _ => None,
1582 }
1583}
1584
1585pub fn eliminate_dead_code(stmts: Vec<Stmt>) -> Vec<Stmt> {
1590 let mut result = Vec::new();
1591 let mut unreachable = false;
1592 for stmt in stmts {
1593 if unreachable { break; }
1594 match stmt {
1595 Stmt::If { ref cond, ref then_body, ref else_body } => {
1596 if let Expr::BoolLit(true) = cond {
1597 result.push(*then_body.clone());
1599 } else if let Expr::BoolLit(false) = cond {
1600 if let Some(e) = else_body { result.push(*e.clone()); }
1602 } else {
1603 result.push(stmt);
1604 }
1605 }
1606 Stmt::Return(_) | Stmt::Break | Stmt::Continue | Stmt::Discard => {
1607 result.push(stmt);
1608 unreachable = true;
1609 }
1610 Stmt::Block(inner) => {
1611 let cleaned = eliminate_dead_code(inner);
1612 result.push(Stmt::Block(cleaned));
1613 }
1614 other => result.push(other),
1615 }
1616 }
1617 result
1618}
1619
1620pub struct CSEPass {
1625 pub expressions: HashMap<String, String>, pub temps: Vec<(String, Expr)>,
1627 pub counter: u32,
1628}
1629
1630impl CSEPass {
1631 pub fn new() -> Self { Self { expressions: HashMap::new(), temps: Vec::new(), counter: 0 } }
1632 pub fn expr_key(expr: &Expr) -> Option<String> {
1633 match expr {
1634 Expr::Binary { op, left, right } => {
1635 let lk = Self::expr_key(left)?;
1636 let rk = Self::expr_key(right)?;
1637 Some(format!("{:?}_{}_{}", op, lk, rk))
1638 }
1639 Expr::Call { function, args } => {
1640 let arg_keys: Vec<String> = args.iter().filter_map(|a| Self::expr_key(a)).collect();
1641 if arg_keys.len() == args.len() { Some(format!("{}({})", function, arg_keys.join(","))) } else { None }
1642 }
1643 Expr::FloatLit(v) => Some(format!("f{:.6}", v)),
1644 Expr::IntLit(v) => Some(format!("i{}", v)),
1645 Expr::BoolLit(v) => Some(format!("b{}", v)),
1646 Expr::Ident(n) => Some(n.clone()),
1647 _ => None,
1648 }
1649 }
1650 pub fn process_expr(&mut self, expr: Expr) -> Expr {
1651 match expr {
1652 Expr::Binary { op, left, right } => {
1653 let left = self.process_expr(*left);
1654 let right = self.process_expr(*right);
1655 let folded = Expr::Binary { op, left: Box::new(left), right: Box::new(right) };
1656 if let Some(key) = Self::expr_key(&folded) {
1657 if let Some(temp) = self.expressions.get(&key) {
1658 return Expr::Ident(temp.clone());
1659 }
1660 let temp_name = format!("_cse_{}", self.counter);
1661 self.counter += 1;
1662 self.expressions.insert(key, temp_name.clone());
1663 self.temps.push((temp_name.clone(), folded));
1664 Expr::Ident(temp_name)
1665 } else { folded }
1666 }
1667 Expr::Call { function, args } => {
1668 let args: Vec<Expr> = args.into_iter().map(|a| self.process_expr(a)).collect();
1669 let e = Expr::Call { function, args };
1670 if let Some(key) = Self::expr_key(&e) {
1671 if let Some(temp) = self.expressions.get(&key) {
1672 return Expr::Ident(temp.clone());
1673 }
1674 }
1675 e
1676 }
1677 other => other,
1678 }
1679 }
1680}
1681
1682pub struct GlslEmitter {
1687 pub output: String,
1688 pub indent: usize,
1689 pub version: u32,
1690 pub es: bool,
1691}
1692
1693impl GlslEmitter {
1694 pub fn new(version: u32, es: bool) -> Self { Self { output: String::new(), indent: 0, version, es } }
1695
1696 fn write(&mut self, s: &str) { self.output.push_str(s); }
1697 fn writeln(&mut self, s: &str) {
1698 let indent: String = " ".repeat(self.indent * 4);
1699 self.output.push_str(&indent);
1700 self.output.push_str(s);
1701 self.output.push('\n');
1702 }
1703 fn indent_in(&mut self) { self.indent += 1; }
1704 fn indent_out(&mut self) { if self.indent > 0 { self.indent -= 1; } }
1705
1706 pub fn emit_header(&mut self, ast: &ShaderAst) {
1707 if self.es {
1708 self.writeln(&format!("#version {} es", self.version));
1709 } else {
1710 self.writeln(&format!("#version {} core", self.version));
1711 }
1712 for (name, val) in &ast.defines {
1713 self.writeln(&format!("#define {} {}", name, val));
1714 }
1715 }
1716
1717 pub fn emit_type(&self, ty: &ShaderType) -> String {
1718 ty.name().to_string()
1719 }
1720
1721 pub fn emit_struct(&mut self, s: &StructDecl) {
1722 self.writeln(&format!("struct {} {{", s.name));
1723 self.indent_in();
1724 for f in &s.fields {
1725 let arr = if let Some(n) = f.array_size { format!("[{}]", n) } else { String::new() };
1726 self.writeln(&format!("{} {}{};", self.emit_type(&f.ty), f.name, arr));
1727 }
1728 self.indent_out();
1729 self.writeln("};");
1730 }
1731
1732 pub fn emit_uniform(&mut self, u: &UniformDecl) {
1733 let layout = if u.binding.is_some() || u.set.is_some() {
1734 let parts: Vec<String> = [
1735 u.set.map(|s| format!("set={}", s)),
1736 u.binding.map(|b| format!("binding={}", b)),
1737 ].iter().filter_map(|x| x.clone()).collect();
1738 if parts.is_empty() { String::new() } else { format!("layout({}) ", parts.join(", ")) }
1739 } else { String::new() };
1740 let arr = if let Some(n) = u.array_size { format!("[{}]", n) } else { String::new() };
1741 self.writeln(&format!("{}uniform {} {}{};", layout, self.emit_type(&u.ty), u.name, arr));
1742 }
1743
1744 pub fn emit_expr(&self, expr: &Expr) -> String {
1745 match expr {
1746 Expr::IntLit(v) => v.to_string(),
1747 Expr::FloatLit(v) => {
1748 if *v == v.floor() && v.abs() < 1e15 { format!("{:.1}", v) } else { format!("{}", v) }
1749 }
1750 Expr::BoolLit(v) => v.to_string(),
1751 Expr::Ident(n) => n.clone(),
1752 Expr::Unary { op, operand } => {
1753 let o = self.emit_expr(operand);
1754 match op {
1755 UnaryOp::Negate => format!("(-{})", o),
1756 UnaryOp::Not => format!("(!{})", o),
1757 UnaryOp::BitNot => format!("(~{})", o),
1758 UnaryOp::PreIncrement => format!("(++{})", o),
1759 UnaryOp::PreDecrement => format!("(--{})", o),
1760 UnaryOp::PostIncrement => format!("({}++)", o),
1761 UnaryOp::PostDecrement => format!("({}--)", o),
1762 }
1763 }
1764 Expr::Binary { op, left, right } => {
1765 let l = self.emit_expr(left);
1766 let r = self.emit_expr(right);
1767 let sym = match op {
1768 BinaryOp::Add => "+", BinaryOp::Sub => "-", BinaryOp::Mul => "*", BinaryOp::Div => "/",
1769 BinaryOp::Mod => "%", BinaryOp::Eq => "==", BinaryOp::Ne => "!=",
1770 BinaryOp::Lt => "<", BinaryOp::Gt => ">", BinaryOp::Le => "<=", BinaryOp::Ge => ">=",
1771 BinaryOp::And => "&&", BinaryOp::Or => "||",
1772 BinaryOp::BitAnd => "&", BinaryOp::BitOr => "|", BinaryOp::BitXor => "^",
1773 BinaryOp::Shl => "<<", BinaryOp::Shr => ">>",
1774 };
1775 format!("({} {} {})", l, sym, r)
1776 }
1777 Expr::Assign { op, target, value } => {
1778 let t = self.emit_expr(target);
1779 let v = self.emit_expr(value);
1780 let sym = match op {
1781 AssignOp::Assign => "=", AssignOp::AddAssign => "+=", AssignOp::SubAssign => "-=",
1782 AssignOp::MulAssign => "*=", AssignOp::DivAssign => "/=", AssignOp::ModAssign => "%=",
1783 AssignOp::AndAssign => "&=", AssignOp::OrAssign => "|=", AssignOp::XorAssign => "^=",
1784 AssignOp::ShlAssign => "<<=", AssignOp::ShrAssign => ">>=",
1785 };
1786 format!("{} {} {}", t, sym, v)
1787 }
1788 Expr::Ternary { cond, then_expr, else_expr } => {
1789 format!("({} ? {} : {})", self.emit_expr(cond), self.emit_expr(then_expr), self.emit_expr(else_expr))
1790 }
1791 Expr::Call { function, args } => {
1792 let arg_strs: Vec<String> = args.iter().map(|a| self.emit_expr(a)).collect();
1793 format!("{}({})", function, arg_strs.join(", "))
1794 }
1795 Expr::Index { array, index } => format!("{}[{}]", self.emit_expr(array), self.emit_expr(index)),
1796 Expr::Field { object, field } => format!("{}.{}", self.emit_expr(object), field),
1797 Expr::Swizzle { object, components } => format!("{}.{}", self.emit_expr(object), components),
1798 Expr::Cast { target_type, expr } => format!("{}({})", target_type.name(), self.emit_expr(expr)),
1799 Expr::Construction { ty, args } => {
1800 let arg_strs: Vec<String> = args.iter().map(|a| self.emit_expr(a)).collect();
1801 format!("{}({})", ty.name(), arg_strs.join(", "))
1802 }
1803 }
1804 }
1805
1806 pub fn emit_stmt(&mut self, stmt: &Stmt) {
1807 match stmt {
1808 Stmt::Expr(e) => {
1809 let s = self.emit_expr(e);
1810 self.writeln(&format!("{};", s));
1811 }
1812 Stmt::Block(stmts) => {
1813 self.writeln("{");
1814 self.indent_in();
1815 for s in stmts { self.emit_stmt(s); }
1816 self.indent_out();
1817 self.writeln("}");
1818 }
1819 Stmt::VarDecl { ty, name, qualifier, init } => {
1820 let qual = match qualifier {
1821 Some(VarQualifier::Const) => "const ".to_string(),
1822 Some(VarQualifier::In) => "in ".to_string(),
1823 Some(VarQualifier::Out) => "out ".to_string(),
1824 Some(VarQualifier::Inout) => "inout ".to_string(),
1825 _ => String::new(),
1826 };
1827 if let Some(init_expr) = init {
1828 self.writeln(&format!("{}{} {} = {};", qual, self.emit_type(ty), name, self.emit_expr(init_expr)));
1829 } else {
1830 self.writeln(&format!("{}{} {};", qual, self.emit_type(ty), name));
1831 }
1832 }
1833 Stmt::If { cond, then_body, else_body } => {
1834 let cond_str = self.emit_expr(cond);
1835 self.writeln(&format!("if ({}) {{", cond_str));
1836 self.indent_in();
1837 self.emit_stmt(then_body);
1838 self.indent_out();
1839 if let Some(else_stmt) = else_body {
1840 self.writeln("} else {");
1841 self.indent_in();
1842 self.emit_stmt(else_stmt);
1843 self.indent_out();
1844 self.writeln("}");
1845 } else {
1846 self.writeln("}");
1847 }
1848 }
1849 Stmt::For { init, cond, step, body } => {
1850 let init_str = if let Some(i) = init {
1851 match i.as_ref() {
1852 Stmt::VarDecl { ty, name, init: Some(e), .. } => format!("{} {} = {}", self.emit_type(ty), name, self.emit_expr(e)),
1853 Stmt::Expr(e) => self.emit_expr(e),
1854 _ => String::new(),
1855 }
1856 } else { String::new() };
1857 let cond_str = cond.as_ref().map(|c| self.emit_expr(c)).unwrap_or_default();
1858 let step_str = step.as_ref().map(|s| self.emit_expr(s)).unwrap_or_default();
1859 self.writeln(&format!("for ({}; {}; {}) {{", init_str, cond_str, step_str));
1860 self.indent_in();
1861 self.emit_stmt(body);
1862 self.indent_out();
1863 self.writeln("}");
1864 }
1865 Stmt::While { cond, body } => {
1866 self.writeln(&format!("while ({}) {{", self.emit_expr(cond)));
1867 self.indent_in();
1868 self.emit_stmt(body);
1869 self.indent_out();
1870 self.writeln("}");
1871 }
1872 Stmt::DoWhile { body, cond } => {
1873 self.writeln("do {");
1874 self.indent_in();
1875 self.emit_stmt(body);
1876 self.indent_out();
1877 self.writeln(&format!("}} while ({});", self.emit_expr(cond)));
1878 }
1879 Stmt::Return(val) => {
1880 if let Some(v) = val {
1881 self.writeln(&format!("return {};", self.emit_expr(v)));
1882 } else {
1883 self.writeln("return;");
1884 }
1885 }
1886 Stmt::Break => self.writeln("break;"),
1887 Stmt::Continue => self.writeln("continue;"),
1888 Stmt::Discard => self.writeln("discard;"),
1889 }
1890 }
1891
1892 pub fn emit_function(&mut self, func: &FunctionDecl) {
1893 let params: Vec<String> = func.params.iter().map(|p| {
1894 let q = match p.qualifier {
1895 Some(VarQualifier::In) => "in ",
1896 Some(VarQualifier::Out) => "out ",
1897 Some(VarQualifier::Inout) => "inout ",
1898 _ => "",
1899 };
1900 format!("{}{} {}", q, self.emit_type(&p.ty), p.name)
1901 }).collect();
1902 self.writeln(&format!("{} {}({}) {{", self.emit_type(&func.return_type), func.name, params.join(", ")));
1903 self.indent_in();
1904 for stmt in &func.body { self.emit_stmt(stmt); }
1905 self.indent_out();
1906 self.writeln("}");
1907 }
1908
1909 pub fn emit_ast(&mut self, ast: &ShaderAst) -> String {
1910 self.emit_header(ast);
1911 self.write("\n");
1912 for s in &ast.structs { self.emit_struct(s); self.write("\n"); }
1913 for u in &ast.uniforms { self.emit_uniform(u); }
1914 if !ast.uniforms.is_empty() { self.write("\n"); }
1915 for a in &ast.attributes {
1916 self.writeln(&format!("in {} {};", self.emit_type(&a.ty), a.name));
1917 }
1918 for v in &ast.varyings {
1919 self.writeln(&format!("out {} {};", self.emit_type(&v.ty), v.name));
1920 }
1921 if !ast.attributes.is_empty() || !ast.varyings.is_empty() { self.write("\n"); }
1922 for func in &ast.functions {
1923 self.emit_function(func);
1924 self.write("\n");
1925 }
1926 self.output.clone()
1927 }
1928}
1929
1930#[derive(Debug, Clone)]
1935pub enum SpvOp {
1936 LoadConst { dest: u32, value: SpvConst },
1937 Load { dest: u32, src_var: u32 },
1938 Store { dest_var: u32, src: u32 },
1939 Add { dest: u32, a: u32, b: u32 },
1940 Sub { dest: u32, a: u32, b: u32 },
1941 Mul { dest: u32, a: u32, b: u32 },
1942 Div { dest: u32, a: u32, b: u32 },
1943 FuncCall { dest: u32, func_id: u32, args: Vec<u32> },
1944 Jump { label: u32 },
1945 JumpIf { cond: u32, label_true: u32, label_false: u32 },
1946 Label { id: u32 },
1947 Return { value: Option<u32> },
1948 Phi { dest: u32, pairs: Vec<(u32, u32)> },
1949 Convert { dest: u32, src: u32, to_type: SpvTypeId },
1950 Negate { dest: u32, src: u32 },
1951 Not { dest: u32, src: u32 },
1952 Eq { dest: u32, a: u32, b: u32 },
1953 Ne { dest: u32, a: u32, b: u32 },
1954 Lt { dest: u32, a: u32, b: u32 },
1955 Gt { dest: u32, a: u32, b: u32 },
1956 Le { dest: u32, a: u32, b: u32 },
1957 Ge { dest: u32, a: u32, b: u32 },
1958}
1959
1960#[derive(Debug, Clone)]
1961pub enum SpvConst {
1962 Float(f32),
1963 Int(i32),
1964 Bool(bool),
1965 Vec2([f32;2]),
1966 Vec3([f32;3]),
1967 Vec4([f32;4]),
1968}
1969
1970#[derive(Debug, Clone, Copy)]
1971pub enum SpvTypeId { Float, Int, Uint, Bool, Vec2, Vec3, Vec4, Mat4 }
1972
1973#[derive(Debug, Clone)]
1974pub struct SpvFunction {
1975 pub id: u32,
1976 pub name: String,
1977 pub return_type: SpvTypeId,
1978 pub instructions: Vec<SpvOp>,
1979}
1980
1981#[derive(Debug, Clone)]
1982pub struct SpvModule {
1983 pub functions: Vec<SpvFunction>,
1984 pub global_variables: Vec<(u32, SpvTypeId, String)>,
1985 pub next_id: u32,
1986}
1987
1988impl SpvModule {
1989 pub fn new() -> Self { Self { functions: Vec::new(), global_variables: Vec::new(), next_id: 1 } }
1990 pub fn alloc_id(&mut self) -> u32 { let id = self.next_id; self.next_id += 1; id }
1991}
1992
1993pub struct SpvCodegen {
1994 pub module: SpvModule,
1995 pub var_map: HashMap<String, u32>,
1996 pub current_fn_instrs: Vec<SpvOp>,
1997 pub label_counter: u32,
1998}
1999
2000impl SpvCodegen {
2001 pub fn new() -> Self { Self { module: SpvModule::new(), var_map: HashMap::new(), current_fn_instrs: Vec::new(), label_counter: 0 } }
2002 pub fn new_label(&mut self) -> u32 { let l = self.label_counter; self.label_counter += 1; l }
2003
2004 pub fn emit_expr_spv(&mut self, expr: &Expr) -> u32 {
2005 match expr {
2006 Expr::FloatLit(v) => {
2007 let dest = self.module.alloc_id();
2008 self.current_fn_instrs.push(SpvOp::LoadConst { dest, value: SpvConst::Float(*v as f32) });
2009 dest
2010 }
2011 Expr::IntLit(v) => {
2012 let dest = self.module.alloc_id();
2013 self.current_fn_instrs.push(SpvOp::LoadConst { dest, value: SpvConst::Int(*v as i32) });
2014 dest
2015 }
2016 Expr::BoolLit(v) => {
2017 let dest = self.module.alloc_id();
2018 self.current_fn_instrs.push(SpvOp::LoadConst { dest, value: SpvConst::Bool(*v) });
2019 dest
2020 }
2021 Expr::Ident(name) => {
2022 let var_id = *self.var_map.get(name).unwrap_or(&0);
2023 let dest = self.module.alloc_id();
2024 self.current_fn_instrs.push(SpvOp::Load { dest, src_var: var_id });
2025 dest
2026 }
2027 Expr::Binary { op, left, right } => {
2028 let a = self.emit_expr_spv(left);
2029 let b = self.emit_expr_spv(right);
2030 let dest = self.module.alloc_id();
2031 let instr = match op {
2032 BinaryOp::Add => SpvOp::Add { dest, a, b },
2033 BinaryOp::Sub => SpvOp::Sub { dest, a, b },
2034 BinaryOp::Mul => SpvOp::Mul { dest, a, b },
2035 BinaryOp::Div => SpvOp::Div { dest, a, b },
2036 BinaryOp::Eq => SpvOp::Eq { dest, a, b },
2037 BinaryOp::Ne => SpvOp::Ne { dest, a, b },
2038 BinaryOp::Lt => SpvOp::Lt { dest, a, b },
2039 BinaryOp::Gt => SpvOp::Gt { dest, a, b },
2040 BinaryOp::Le => SpvOp::Le { dest, a, b },
2041 BinaryOp::Ge => SpvOp::Ge { dest, a, b },
2042 _ => SpvOp::Add { dest, a, b }, };
2044 self.current_fn_instrs.push(instr);
2045 dest
2046 }
2047 Expr::Unary { op, operand } => {
2048 let src = self.emit_expr_spv(operand);
2049 let dest = self.module.alloc_id();
2050 let instr = match op {
2051 UnaryOp::Negate => SpvOp::Negate { dest, src },
2052 UnaryOp::Not => SpvOp::Not { dest, src },
2053 _ => SpvOp::Negate { dest, src },
2054 };
2055 self.current_fn_instrs.push(instr);
2056 dest
2057 }
2058 _ => { self.module.alloc_id() }
2059 }
2060 }
2061}
2062
2063pub struct WgslEmitter {
2068 pub output: String,
2069 pub indent: usize,
2070}
2071
2072impl WgslEmitter {
2073 pub fn new() -> Self { Self { output: String::new(), indent: 0 } }
2074 fn writeln(&mut self, s: &str) {
2075 let indent = " ".repeat(self.indent);
2076 self.output.push_str(&indent);
2077 self.output.push_str(s);
2078 self.output.push('\n');
2079 }
2080 fn indent_in(&mut self) { self.indent += 1; }
2081 fn indent_out(&mut self) { if self.indent > 0 { self.indent -= 1; } }
2082
2083 pub fn wgsl_type(ty: &ShaderType) -> &'static str {
2084 match ty {
2085 ShaderType::Float => "f32",
2086 ShaderType::Int => "i32",
2087 ShaderType::Uint => "u32",
2088 ShaderType::Bool => "bool",
2089 ShaderType::Vec2 => "vec2<f32>",
2090 ShaderType::Vec3 => "vec3<f32>",
2091 ShaderType::Vec4 => "vec4<f32>",
2092 ShaderType::IVec2 => "vec2<i32>",
2093 ShaderType::IVec3 => "vec3<i32>",
2094 ShaderType::IVec4 => "vec4<i32>",
2095 ShaderType::Mat2 => "mat2x2<f32>",
2096 ShaderType::Mat3 => "mat3x3<f32>",
2097 ShaderType::Mat4 => "mat4x4<f32>",
2098 ShaderType::Sampler2D => "texture_2d<f32>",
2099 ShaderType::SamplerCube => "texture_cube<f32>",
2100 _ => "f32",
2101 }
2102 }
2103
2104 pub fn emit_uniform_wgsl(&mut self, u: &UniformDecl, group: u32) {
2105 let binding = u.binding.unwrap_or(0);
2106 self.writeln(&format!("@group({}) @binding({}) var<uniform> {}: {};", group, binding, u.name, Self::wgsl_type(&u.ty)));
2107 }
2108
2109 pub fn emit_function_wgsl(&mut self, func: &FunctionDecl) {
2110 let params: Vec<String> = func.params.iter().map(|p| {
2111 format!("{}: {}", p.name, Self::wgsl_type(&p.ty))
2112 }).collect();
2113 let ret = if func.return_type == ShaderType::Void {
2114 String::new()
2115 } else {
2116 format!(" -> {}", Self::wgsl_type(&func.return_type))
2117 };
2118 self.writeln(&format!("fn {}({}){} {{", func.name, params.join(", "), ret));
2119 self.indent_in();
2120 for stmt in &func.body { self.emit_stmt_wgsl(stmt); }
2121 self.indent_out();
2122 self.writeln("}");
2123 }
2124
2125 fn emit_expr_wgsl(&self, expr: &Expr) -> String {
2126 match expr {
2128 Expr::FloatLit(v) => format!("{:.6}f", v),
2129 Expr::IntLit(v) => format!("{}i", v),
2130 Expr::BoolLit(v) => v.to_string(),
2131 Expr::Ident(n) => n.clone(),
2132 Expr::Binary { op, left, right } => {
2133 let l = self.emit_expr_wgsl(left);
2134 let r = self.emit_expr_wgsl(right);
2135 let sym = match op {
2136 BinaryOp::Add => "+", BinaryOp::Sub => "-", BinaryOp::Mul => "*", BinaryOp::Div => "/",
2137 BinaryOp::Mod => "%", BinaryOp::Eq => "==", BinaryOp::Ne => "!=",
2138 BinaryOp::Lt => "<", BinaryOp::Gt => ">", BinaryOp::Le => "<=", BinaryOp::Ge => ">=",
2139 BinaryOp::And => "&&", BinaryOp::Or => "||",
2140 BinaryOp::BitAnd => "&", BinaryOp::BitOr => "|", BinaryOp::BitXor => "^",
2141 BinaryOp::Shl => "<<", BinaryOp::Shr => ">>",
2142 };
2143 format!("({} {} {})", l, sym, r)
2144 }
2145 Expr::Call { function, args } => {
2146 let wgsl_fn = match function.as_str() {
2147 "sin" => "sin", "cos" => "cos", "sqrt" => "sqrt", "abs" => "abs",
2148 "mix" => "mix", "clamp" => "clamp", "dot" => "dot", "cross" => "cross",
2149 "normalize" => "normalize", "length" => "length", "pow" => "pow",
2150 "floor" => "floor", "ceil" => "ceil", "fract" => "fract",
2151 "smoothstep" => "smoothstep", "max" => "max", "min" => "min",
2152 "texture" => "textureSample",
2153 other => other,
2154 };
2155 let arg_strs: Vec<String> = args.iter().map(|a| self.emit_expr_wgsl(a)).collect();
2156 format!("{}({})", wgsl_fn, arg_strs.join(", "))
2157 }
2158 Expr::Field { object, field } => format!("{}.{}", self.emit_expr_wgsl(object), field),
2159 Expr::Swizzle { object, components } => format!("{}.{}", self.emit_expr_wgsl(object), components),
2160 Expr::Construction { ty, args } => {
2161 let arg_strs: Vec<String> = args.iter().map(|a| self.emit_expr_wgsl(a)).collect();
2162 format!("{}({})", Self::wgsl_type(ty), arg_strs.join(", "))
2163 }
2164 _ => String::from("/* expr */"),
2165 }
2166 }
2167
2168 fn emit_stmt_wgsl(&mut self, stmt: &Stmt) {
2169 match stmt {
2170 Stmt::Expr(e) => { let s = self.emit_expr_wgsl(e); self.writeln(&format!("{};", s)); }
2171 Stmt::VarDecl { ty, name, init, qualifier } => {
2172 let kw = if matches!(qualifier, Some(VarQualifier::Const)) { "let" } else { "var" };
2173 if let Some(init_expr) = init {
2174 self.writeln(&format!("{} {}: {} = {};", kw, name, Self::wgsl_type(ty), self.emit_expr_wgsl(init_expr)));
2175 } else {
2176 self.writeln(&format!("{} {}: {};", kw, name, Self::wgsl_type(ty)));
2177 }
2178 }
2179 Stmt::Return(val) => {
2180 if let Some(v) = val { self.writeln(&format!("return {};", self.emit_expr_wgsl(v))); }
2181 else { self.writeln("return;"); }
2182 }
2183 Stmt::If { cond, then_body, else_body } => {
2184 self.writeln(&format!("if ({}) {{", self.emit_expr_wgsl(cond)));
2185 self.indent_in(); self.emit_stmt_wgsl(then_body); self.indent_out();
2186 if let Some(e) = else_body {
2187 self.writeln("} else {");
2188 self.indent_in(); self.emit_stmt_wgsl(e); self.indent_out();
2189 }
2190 self.writeln("}");
2191 }
2192 Stmt::For { init, cond, step, body } => {
2193 let cond_str = cond.as_ref().map(|c| self.emit_expr_wgsl(c)).unwrap_or_default();
2194 self.writeln(&format!("loop {{"));
2195 self.indent_in();
2196 self.writeln(&format!("if (!{}) {{ break; }}", cond_str));
2197 self.emit_stmt_wgsl(body);
2198 self.indent_out();
2199 self.writeln("}");
2200 }
2201 Stmt::Block(stmts) => {
2202 self.writeln("{");
2203 self.indent_in();
2204 for s in stmts { self.emit_stmt_wgsl(s); }
2205 self.indent_out();
2206 self.writeln("}");
2207 }
2208 Stmt::Break => self.writeln("break;"),
2209 Stmt::Continue => self.writeln("continue;"),
2210 Stmt::Discard => self.writeln("discard;"),
2211 _ => {}
2212 }
2213 }
2214
2215 pub fn emit_ast_wgsl(&mut self, ast: &ShaderAst) -> String {
2216 self.writeln("// WGSL generated by ShaderCompiler");
2217 for u in &ast.uniforms { self.emit_uniform_wgsl(u, 0); }
2218 for func in &ast.functions { self.emit_function_wgsl(func); self.output.push('\n'); }
2219 self.output.clone()
2220 }
2221}
2222
2223#[derive(Debug, Clone)]
2228pub struct ReflectedUniform {
2229 pub name: String,
2230 pub ty: ShaderType,
2231 pub binding: u32,
2232 pub set: u32,
2233 pub array_size: u32,
2234 pub size_bytes: u32,
2235}
2236
2237impl ReflectedUniform {
2238 pub fn size_of_type(ty: &ShaderType) -> u32 {
2239 match ty {
2240 ShaderType::Float | ShaderType::Int | ShaderType::Uint | ShaderType::Bool => 4,
2241 ShaderType::Vec2 | ShaderType::IVec2 | ShaderType::UVec2 => 8,
2242 ShaderType::Vec3 | ShaderType::IVec3 | ShaderType::UVec3 => 12,
2243 ShaderType::Vec4 | ShaderType::IVec4 | ShaderType::UVec4 => 16,
2244 ShaderType::Mat2 => 16,
2245 ShaderType::Mat3 => 36,
2246 ShaderType::Mat4 => 64,
2247 _ => 0,
2248 }
2249 }
2250}
2251
2252#[derive(Debug, Clone)]
2253pub struct ReflectedAttribute {
2254 pub name: String,
2255 pub ty: ShaderType,
2256 pub location: u32,
2257 pub format: VertexFormat,
2258}
2259
2260#[derive(Debug, Clone, Copy, PartialEq)]
2261pub enum VertexFormat {
2262 Float32, Float32x2, Float32x3, Float32x4,
2263 Sint32, Sint32x2, Sint32x3, Sint32x4,
2264 Uint32, Uint32x2, Uint32x3, Uint32x4,
2265}
2266
2267#[derive(Debug, Clone, Default)]
2268pub struct ShaderReflection {
2269 pub uniforms: Vec<ReflectedUniform>,
2270 pub attributes: Vec<ReflectedAttribute>,
2271 pub varyings: Vec<ReflectedAttribute>,
2272 pub used_builtins: Vec<String>,
2273 pub struct_layouts: Vec<String>,
2274 pub push_constants: Vec<ReflectedUniform>,
2275 pub storage_buffers: Vec<ReflectedUniform>,
2276 pub entry_points: Vec<String>,
2277}
2278
2279impl ShaderReflection {
2280 pub fn new() -> Self { Self::default() }
2281
2282 pub fn add_uniform(&mut self, u: ReflectedUniform) {
2283 self.uniforms.push(u);
2284 }
2285
2286 pub fn add_attribute(&mut self, a: ReflectedAttribute) {
2287 self.attributes.push(a);
2288 }
2289
2290
2291}
2292
2293#[derive(Clone, Debug)]
2296pub struct ShaderProgram {
2297 pub id: u32,
2298 pub name: String,
2299 pub vertex_source: String,
2300 pub fragment_source: String,
2301 pub geometry_source: Option<String>,
2302 pub compute_source: Option<String>,
2303 pub defines: HashMap<String, String>,
2304 pub version: String,
2305 pub compiled: bool,
2306 pub compile_errors: Vec<String>,
2307 pub compile_warnings: Vec<String>,
2308}
2309
2310impl ShaderProgram {
2311 pub fn new(id: u32, name: impl Into<String>) -> Self {
2312 Self { id, name: name.into(), vertex_source: String::new(), fragment_source: String::new(), geometry_source: None, compute_source: None, defines: HashMap::new(), version: "450".into(), compiled: false, compile_errors: Vec::new(), compile_warnings: Vec::new() }
2313 }
2314 pub fn with_vertex(mut self, src: impl Into<String>) -> Self { self.vertex_source = src.into(); self }
2315 pub fn with_fragment(mut self, src: impl Into<String>) -> Self { self.fragment_source = src.into(); self }
2316 pub fn add_define(&mut self, key: impl Into<String>, val: impl Into<String>) { self.defines.insert(key.into(), val.into()); }
2317 pub fn is_compute(&self) -> bool { self.compute_source.is_some() }
2318 pub fn has_geometry(&self) -> bool { self.geometry_source.is_some() }
2319 pub fn is_valid(&self) -> bool { self.compiled && self.compile_errors.is_empty() }
2320 pub fn error_count(&self) -> usize { self.compile_errors.len() }
2321 pub fn warning_count(&self) -> usize { self.compile_warnings.len() }
2322 pub fn add_error(&mut self, err: impl Into<String>) { self.compile_errors.push(err.into()); }
2323 pub fn add_warning(&mut self, warn: impl Into<String>) { self.compile_warnings.push(warn.into()); }
2324 pub fn clear_diagnostics(&mut self) { self.compile_errors.clear(); self.compile_warnings.clear(); }
2325 pub fn mark_compiled(&mut self) { self.compiled = true; }
2326}
2327
2328#[derive(Clone, Debug)]
2329pub struct ShaderProgramRegistry {
2330 pub programs: HashMap<u32, ShaderProgram>,
2331 pub next_id: u32,
2332 pub active_program: Option<u32>,
2333}
2334
2335impl ShaderProgramRegistry {
2336 pub fn new() -> Self { Self { programs: HashMap::new(), next_id: 1, active_program: None } }
2337 pub fn add(&mut self, prog: ShaderProgram) -> u32 {
2338 let id = self.next_id; self.next_id += 1;
2339 self.programs.insert(id, prog);
2340 id
2341 }
2342 pub fn get(&self, id: u32) -> Option<&ShaderProgram> { self.programs.get(&id) }
2343 pub fn get_mut(&mut self, id: u32) -> Option<&mut ShaderProgram> { self.programs.get_mut(&id) }
2344 pub fn find_by_name(&self, name: &str) -> Option<&ShaderProgram> { self.programs.values().find(|p| p.name == name) }
2345 pub fn count(&self) -> usize { self.programs.len() }
2346 pub fn valid_count(&self) -> usize { self.programs.values().filter(|p| p.is_valid()).count() }
2347 pub fn set_active(&mut self, id: u32) { self.active_program = Some(id); }
2348 pub fn remove(&mut self, id: u32) -> bool { self.programs.remove(&id).is_some() }
2349}
2350
2351impl Default for ShaderProgramRegistry {
2352 fn default() -> Self { Self::new() }
2353}
2354
2355#[derive(Clone, Debug, PartialEq)]
2358pub enum ShaderDataType {
2359 Void,
2360 Bool, BVec2, BVec3, BVec4,
2361 Int, IVec2, IVec3, IVec4,
2362 UInt, UVec2, UVec3, UVec4,
2363 Float, Vec2, Vec3, Vec4,
2364 Mat2, Mat3, Mat4,
2365 Mat2x3, Mat2x4, Mat3x2, Mat3x4, Mat4x2, Mat4x3,
2366 Sampler2D, Sampler3D, SamplerCube, Sampler2DArray, SamplerShadow,
2367 Image2D, Image3D, ImageCube,
2368 Struct(String),
2369 Array(Box<ShaderDataType>, Option<u32>),
2370}
2371
2372impl ShaderDataType {
2373 pub fn byte_size(&self) -> u32 {
2374 match self {
2375 ShaderDataType::Bool | ShaderDataType::Int | ShaderDataType::UInt | ShaderDataType::Float => 4,
2376 ShaderDataType::BVec2 | ShaderDataType::IVec2 | ShaderDataType::UVec2 | ShaderDataType::Vec2 => 8,
2377 ShaderDataType::BVec3 | ShaderDataType::IVec3 | ShaderDataType::UVec3 | ShaderDataType::Vec3 => 12,
2378 ShaderDataType::BVec4 | ShaderDataType::IVec4 | ShaderDataType::UVec4 | ShaderDataType::Vec4 => 16,
2379 ShaderDataType::Mat2 => 16, ShaderDataType::Mat3 => 36, ShaderDataType::Mat4 => 64,
2380 ShaderDataType::Array(inner, Some(n)) => inner.byte_size() * n,
2381 _ => 0,
2382 }
2383 }
2384 pub fn is_sampler(&self) -> bool { matches!(self, ShaderDataType::Sampler2D | ShaderDataType::Sampler3D | ShaderDataType::SamplerCube | ShaderDataType::Sampler2DArray | ShaderDataType::SamplerShadow) }
2385 pub fn is_matrix(&self) -> bool { matches!(self, ShaderDataType::Mat2 | ShaderDataType::Mat3 | ShaderDataType::Mat4 | ShaderDataType::Mat2x3 | ShaderDataType::Mat2x4 | ShaderDataType::Mat3x2 | ShaderDataType::Mat3x4 | ShaderDataType::Mat4x2 | ShaderDataType::Mat4x3) }
2386 pub fn is_vector(&self) -> bool { matches!(self, ShaderDataType::Vec2 | ShaderDataType::Vec3 | ShaderDataType::Vec4 | ShaderDataType::IVec2 | ShaderDataType::IVec3 | ShaderDataType::IVec4 | ShaderDataType::UVec2 | ShaderDataType::UVec3 | ShaderDataType::UVec4 | ShaderDataType::BVec2 | ShaderDataType::BVec3 | ShaderDataType::BVec4) }
2387 pub fn glsl_name(&self) -> &'static str {
2388 match self {
2389 ShaderDataType::Void => "void", ShaderDataType::Bool => "bool",
2390 ShaderDataType::Int => "int", ShaderDataType::UInt => "uint",
2391 ShaderDataType::Float => "float", ShaderDataType::Vec2 => "vec2",
2392 ShaderDataType::Vec3 => "vec3", ShaderDataType::Vec4 => "vec4",
2393 ShaderDataType::Mat4 => "mat4", ShaderDataType::Mat3 => "mat3",
2394 ShaderDataType::Mat2 => "mat2", ShaderDataType::Sampler2D => "sampler2D",
2395 ShaderDataType::SamplerCube => "samplerCube",
2396 _ => "unknown",
2397 }
2398 }
2399}
2400
2401#[derive(Clone, Debug)]
2404pub struct ShaderVariable {
2405 pub name: String,
2406 pub data_type: ShaderDataType,
2407 pub qualifier: ShaderQualifier,
2408 pub location: Option<u32>,
2409 pub binding: Option<u32>,
2410 pub set: Option<u32>,
2411 pub is_builtin: bool,
2412 pub comment: String,
2413}
2414
2415#[derive(Clone, Debug, PartialEq)]
2416pub enum ShaderQualifier { In, Out, InOut, Uniform, Const, Buffer, Shared }
2417
2418impl ShaderVariable {
2419 pub fn uniform(name: impl Into<String>, data_type: ShaderDataType) -> Self {
2420 Self { name: name.into(), data_type, qualifier: ShaderQualifier::Uniform, location: None, binding: None, set: None, is_builtin: false, comment: String::new() }
2421 }
2422 pub fn input(name: impl Into<String>, data_type: ShaderDataType, location: u32) -> Self {
2423 Self { name: name.into(), data_type, qualifier: ShaderQualifier::In, location: Some(location), binding: None, set: None, is_builtin: false, comment: String::new() }
2424 }
2425 pub fn output(name: impl Into<String>, data_type: ShaderDataType, location: u32) -> Self {
2426 Self { name: name.into(), data_type, qualifier: ShaderQualifier::Out, location: Some(location), binding: None, set: None, is_builtin: false, comment: String::new() }
2427 }
2428 pub fn with_binding(mut self, binding: u32, set: u32) -> Self { self.binding = Some(binding); self.set = Some(set); self }
2429 pub fn is_uniform(&self) -> bool { self.qualifier == ShaderQualifier::Uniform }
2430 pub fn is_input(&self) -> bool { self.qualifier == ShaderQualifier::In }
2431 pub fn is_output(&self) -> bool { self.qualifier == ShaderQualifier::Out }
2432 pub fn size_bytes(&self) -> u32 { self.data_type.byte_size() }
2433}
2434
2435#[derive(Clone, Debug)]
2438pub struct ShaderFunction {
2439 pub name: String,
2440 pub return_type: ShaderDataType,
2441 pub parameters: Vec<ShaderVariable>,
2442 pub body: String,
2443 pub is_builtin: bool,
2444 pub line_start: u32,
2445 pub line_end: u32,
2446 pub comment: String,
2447}
2448
2449impl ShaderFunction {
2450 pub fn new(name: impl Into<String>, return_type: ShaderDataType) -> Self {
2451 Self { name: name.into(), return_type, parameters: Vec::new(), body: String::new(), is_builtin: false, line_start: 0, line_end: 0, comment: String::new() }
2452 }
2453 pub fn add_param(&mut self, param: ShaderVariable) { self.parameters.push(param); }
2454 pub fn param_count(&self) -> usize { self.parameters.len() }
2455 pub fn is_main(&self) -> bool { self.name == "main" }
2456 pub fn signature(&self) -> String {
2457 let params: Vec<_> = self.parameters.iter().map(|p| format!("{} {}", p.data_type.glsl_name(), p.name)).collect();
2458 format!("{} {}({})", self.return_type.glsl_name(), self.name, params.join(", "))
2459 }
2460}
2461
2462#[derive(Clone, Debug)]
2465pub struct ShaderStruct {
2466 pub name: String,
2467 pub fields: Vec<ShaderVariable>,
2468 pub comment: String,
2469 pub std140_layout: bool,
2470}
2471
2472impl ShaderStruct {
2473 pub fn new(name: impl Into<String>) -> Self { Self { name: name.into(), fields: Vec::new(), comment: String::new(), std140_layout: false } }
2474 pub fn add_field(&mut self, field: ShaderVariable) { self.fields.push(field); }
2475 pub fn size_bytes(&self) -> u32 { self.fields.iter().map(|f| f.size_bytes()).sum() }
2476 pub fn std140_size(&self) -> u32 {
2477 let mut size = 0u32;
2478 for f in &self.fields {
2479 let field_size = f.size_bytes();
2480 let align = field_size.max(4).next_power_of_two().min(16);
2481 size = (size + align - 1) & !(align - 1);
2482 size += field_size;
2483 }
2484 (size + 15) & !15
2485 }
2486 pub fn field_count(&self) -> usize { self.fields.len() }
2487 pub fn has_field(&self, name: &str) -> bool { self.fields.iter().any(|f| f.name == name) }
2488 pub fn generate_glsl(&self) -> String {
2489 let mut s = format!("struct {} {{\n", self.name);
2490 for f in &self.fields { s += &format!(" {} {};\n", f.data_type.glsl_name(), f.name); }
2491 s += "};\n";
2492 s
2493 }
2494}
2495
2496#[derive(Clone, Debug)]
2499pub struct ShaderDiagnostic {
2500 pub kind: DiagnosticKind,
2501 pub message: String,
2502 pub line: u32,
2503 pub column: u32,
2504 pub file: String,
2505 pub code: u32,
2506 pub suggestion: Option<String>,
2507}
2508
2509#[derive(Clone, Debug, PartialEq)]
2510pub enum DiagnosticKind { Error, Warning, Info, Hint }
2511
2512impl ShaderDiagnostic {
2513 pub fn error(msg: impl Into<String>, line: u32) -> Self {
2514 Self { kind: DiagnosticKind::Error, message: msg.into(), line, column: 0, file: String::new(), code: 0, suggestion: None }
2515 }
2516 pub fn warning(msg: impl Into<String>, line: u32) -> Self {
2517 Self { kind: DiagnosticKind::Warning, message: msg.into(), line, column: 0, file: String::new(), code: 0, suggestion: None }
2518 }
2519 pub fn with_suggestion(mut self, s: impl Into<String>) -> Self { self.suggestion = Some(s.into()); self }
2520 pub fn is_error(&self) -> bool { self.kind == DiagnosticKind::Error }
2521 pub fn is_warning(&self) -> bool { self.kind == DiagnosticKind::Warning }
2522 pub fn format(&self) -> String {
2523 let prefix = match self.kind { DiagnosticKind::Error => "error", DiagnosticKind::Warning => "warning", DiagnosticKind::Info => "info", DiagnosticKind::Hint => "hint" };
2524 format!("[{}] {}:{} — {}", prefix, self.file, self.line, self.message)
2525 }
2526}
2527
2528#[derive(Clone, Debug, Default)]
2529pub struct DiagnosticList {
2530 pub items: Vec<ShaderDiagnostic>,
2531}
2532
2533impl DiagnosticList {
2534 pub fn new() -> Self { Self::default() }
2535 pub fn push(&mut self, d: ShaderDiagnostic) { self.items.push(d); }
2536 pub fn errors(&self) -> Vec<&ShaderDiagnostic> { self.items.iter().filter(|d| d.is_error()).collect() }
2537 pub fn warnings(&self) -> Vec<&ShaderDiagnostic> { self.items.iter().filter(|d| d.is_warning()).collect() }
2538 pub fn has_errors(&self) -> bool { self.items.iter().any(|d| d.is_error()) }
2539 pub fn error_count(&self) -> usize { self.items.iter().filter(|d| d.is_error()).count() }
2540 pub fn warning_count(&self) -> usize { self.items.iter().filter(|d| d.is_warning()).count() }
2541 pub fn clear(&mut self) { self.items.clear(); }
2542 pub fn total(&self) -> usize { self.items.len() }
2543}
2544
2545#[derive(Clone, Debug)]
2548pub struct ShaderPreprocessor {
2549 pub defines: HashMap<String, String>,
2550 pub include_paths: Vec<String>,
2551 pub max_include_depth: u32,
2552 pub strip_comments: bool,
2553 pub expand_macros: bool,
2554}
2555
2556impl ShaderPreprocessor {
2557 pub fn new() -> Self {
2558 Self { defines: HashMap::new(), include_paths: Vec::new(), max_include_depth: 10, strip_comments: true, expand_macros: true }
2559 }
2560 pub fn define(&mut self, key: impl Into<String>, value: impl Into<String>) { self.defines.insert(key.into(), value.into()); }
2561 pub fn undefine(&mut self, key: &str) { self.defines.remove(key); }
2562 pub fn add_include_path(&mut self, path: impl Into<String>) { self.include_paths.push(path.into()); }
2563 pub fn preprocess(&self, source: &str) -> String {
2564 let mut output = String::with_capacity(source.len());
2565 for line in source.lines() {
2566 let trimmed = line.trim();
2567 if trimmed.starts_with("#define") {
2568 output.push_str(line);
2569 } else if trimmed.starts_with("//") && self.strip_comments {
2570 } else {
2572 let mut processed = line.to_string();
2573 if self.expand_macros {
2574 for (k, v) in &self.defines { processed = processed.replace(k.as_str(), v.as_str()); }
2575 }
2576 output.push_str(&processed);
2577 }
2578 output.push('\n');
2579 }
2580 output
2581 }
2582 pub fn is_defined(&self, key: &str) -> bool { self.defines.contains_key(key) }
2583 pub fn define_count(&self) -> usize { self.defines.len() }
2584}
2585
2586impl Default for ShaderPreprocessor {
2587 fn default() -> Self { Self::new() }
2588}
2589
2590#[derive(Clone, Debug)]
2593pub struct UniformBlock {
2594 pub name: String,
2595 pub binding: u32,
2596 pub set: u32,
2597 pub fields: Vec<ShaderVariable>,
2598 pub std140: bool,
2599 pub dynamic_offset: bool,
2600}
2601
2602impl UniformBlock {
2603 pub fn new(name: impl Into<String>, binding: u32) -> Self {
2604 Self { name: name.into(), binding, set: 0, fields: Vec::new(), std140: true, dynamic_offset: false }
2605 }
2606 pub fn add_field(&mut self, f: ShaderVariable) { self.fields.push(f); }
2607 pub fn size_bytes(&self) -> u32 {
2608 let mut size = 0u32;
2609 for f in &self.fields {
2610 let fs = f.size_bytes();
2611 let align = fs.max(4).next_power_of_two().min(16);
2612 size = (size + align - 1) & !(align - 1);
2613 size += fs;
2614 }
2615 (size + 15) & !15
2616 }
2617 pub fn generate_glsl(&self) -> String {
2618 let mut s = format!("layout(std140, binding={}) uniform {} {{\n", self.binding, self.name);
2619 for f in &self.fields { s += &format!(" {} {};\n", f.data_type.glsl_name(), f.name); }
2620 s += "};\n";
2621 s
2622 }
2623 pub fn field_count(&self) -> usize { self.fields.len() }
2624}
2625
2626#[derive(Clone, Debug)]
2629pub struct PipelineState {
2630 pub blend_enabled: bool,
2631 pub blend_src: BlendFactor,
2632 pub blend_dst: BlendFactor,
2633 pub blend_op: BlendOp,
2634 pub depth_test: bool,
2635 pub depth_write: bool,
2636 pub depth_func: CompareFunc,
2637 pub cull_mode: CullMode,
2638 pub front_face: FrontFace,
2639 pub scissor_test: bool,
2640 pub stencil_test: bool,
2641 pub polygon_mode: PolygonMode,
2642 pub line_width: f32,
2643 pub point_size: f32,
2644}
2645
2646#[derive(Clone, Debug, PartialEq)]
2647pub enum BlendFactor { Zero, One, SrcAlpha, OneMinusSrcAlpha, DstAlpha, OneMinusDstAlpha, SrcColor, OneMinusSrcColor, ConstantAlpha }
2648#[derive(Clone, Debug, PartialEq)]
2649pub enum BlendOp { Add, Subtract, ReverseSubtract, Min, Max }
2650#[derive(Clone, Debug, PartialEq)]
2651pub enum CompareFunc { Never, Less, Equal, LessEqual, Greater, NotEqual, GreaterEqual, Always }
2652#[derive(Clone, Debug, PartialEq)]
2653pub enum CullMode { None, Front, Back, FrontAndBack }
2654#[derive(Clone, Debug, PartialEq)]
2655pub enum FrontFace { Clockwise, CounterClockwise }
2656#[derive(Clone, Debug, PartialEq)]
2657pub enum PolygonMode { Fill, Line, Point }
2658
2659impl Default for PipelineState {
2660 fn default() -> Self {
2661 Self { blend_enabled: false, blend_src: BlendFactor::SrcAlpha, blend_dst: BlendFactor::OneMinusSrcAlpha, blend_op: BlendOp::Add, depth_test: true, depth_write: true, depth_func: CompareFunc::Less, cull_mode: CullMode::Back, front_face: FrontFace::CounterClockwise, scissor_test: false, stencil_test: false, polygon_mode: PolygonMode::Fill, line_width: 1.0, point_size: 1.0 }
2662 }
2663}
2664
2665impl PipelineState {
2666 pub fn transparent() -> Self { Self { blend_enabled: true, depth_write: false, ..Default::default() } }
2667 pub fn additive() -> Self { Self { blend_enabled: true, blend_src: BlendFactor::One, blend_dst: BlendFactor::One, blend_op: BlendOp::Add, depth_write: false, ..Default::default() } }
2668 pub fn wireframe() -> Self { Self { polygon_mode: PolygonMode::Line, cull_mode: CullMode::None, ..Default::default() } }
2669 pub fn opaque() -> Self { Self::default() }
2670 pub fn is_transparent(&self) -> bool { self.blend_enabled }
2671 pub fn needs_sort(&self) -> bool { self.blend_enabled && !self.depth_write }
2672}
2673
2674pub const SHADER_MAX_UNIFORMS: usize = 128;
2677pub const SHADER_MAX_ATTRIBUTES: usize = 32;
2678pub const SHADER_MAX_VARYINGS: usize = 32;
2679pub const SHADER_MAX_TEXTURE_UNITS: usize = 32;
2680pub const SHADER_MAX_UNIFORM_BLOCK_SIZE: u32 = 65536;
2681pub const SHADER_MAX_INCLUDE_DEPTH: u32 = 16;
2682pub const SHADER_VERSION_GLSL: &str = "450";
2683pub const SHADER_VERSION_WGSL: &str = "1.0";
2684pub const SHADER_MAX_SOURCE_LINES: usize = 10000;
2685pub const SHADER_MAX_FUNCTIONS: usize = 256;
2686pub const SHADER_MAX_STRUCTS: usize = 64;
2687pub const SHADER_MAX_PROGRAMS: usize = 1024;
2688pub const SHADER_CACHE_SIZE: usize = 256;
2689pub const SHADER_MAX_PUSH_CONSTANT_SIZE: u32 = 128;
2690
2691pub fn shader_compiler_info() -> &'static str { "ShaderCompiler v1.0 — GLSL/WGSL, preprocessor, reflection, pipeline state" }
2692
2693
2694#[derive(Clone, Debug)]
2697pub enum ShaderExpr {
2698 Literal(ShaderLiteral),
2699 Ident(String),
2700 Binary { op: BinaryOp, lhs: Box<ShaderExpr>, rhs: Box<ShaderExpr> },
2701 Unary { op: UnaryOp, expr: Box<ShaderExpr> },
2702 Call { name: String, args: Vec<ShaderExpr> },
2703 Index { array: Box<ShaderExpr>, index: Box<ShaderExpr> },
2704 Field { object: Box<ShaderExpr>, field: String },
2705 Ternary { cond: Box<ShaderExpr>, then: Box<ShaderExpr>, else_: Box<ShaderExpr> },
2706 Cast { to: ShaderDataType, expr: Box<ShaderExpr> },
2707 Assign { target: Box<ShaderExpr>, value: Box<ShaderExpr> },
2708}
2709
2710#[derive(Clone, Debug)]
2711pub enum ShaderLiteral {
2712 Int(i64), Float(f64), Bool(bool), String(String),
2713}
2714
2715#[derive(Clone, Debug, PartialEq)]
2716pub enum BinaryOpEx {
2717 Add, Sub, Mul, Div, Mod,
2718 Eq, Ne, Lt, Gt, Le, Ge,
2719 And, Or, BitAnd, BitOr, BitXor,
2720 Shl, Shr,
2721 AddAssign, SubAssign, MulAssign, DivAssign,
2722}
2723
2724#[derive(Clone, Debug, PartialEq)]
2725pub enum UnaryOpEx { Neg, Not, BitNot, PreInc, PreDec, PostInc, PostDec }
2726
2727impl BinaryOpEx {
2728 pub fn is_comparison(&self) -> bool { matches!(self, BinaryOpEx::Eq | BinaryOpEx::Ne | BinaryOpEx::Lt | BinaryOpEx::Gt | BinaryOpEx::Le | BinaryOpEx::Ge) }
2729 pub fn is_logical(&self) -> bool { matches!(self, BinaryOpEx::And | BinaryOpEx::Or) }
2730 pub fn is_arithmetic(&self) -> bool { matches!(self, BinaryOpEx::Add | BinaryOpEx::Sub | BinaryOpEx::Mul | BinaryOpEx::Div | BinaryOpEx::Mod) }
2731 pub fn glsl_symbol(&self) -> &'static str {
2732 match self {
2733 BinaryOpEx::Add => "+", BinaryOpEx::Sub => "-", BinaryOpEx::Mul => "*", BinaryOpEx::Div => "/",
2734 BinaryOpEx::Mod => "%", BinaryOpEx::Eq => "==", BinaryOpEx::Ne => "!=", BinaryOpEx::Lt => "<",
2735 BinaryOpEx::Gt => ">", BinaryOpEx::Le => "<=", BinaryOpEx::Ge => ">=", BinaryOpEx::And => "&&",
2736 BinaryOpEx::Or => "||", BinaryOpEx::BitAnd => "&", BinaryOpEx::BitOr => "|", BinaryOpEx::BitXor => "^",
2737 BinaryOpEx::Shl => "<<", BinaryOpEx::Shr => ">>", BinaryOpEx::AddAssign => "+=",
2738 BinaryOpEx::SubAssign => "-=", BinaryOpEx::MulAssign => "*=", BinaryOpEx::DivAssign => "/=",
2739 }
2740 }
2741}
2742
2743#[derive(Clone, Debug)]
2744pub enum ShaderStmt {
2745 Decl { var: ShaderVariable, initializer: Option<ShaderExpr> },
2746 Expr(ShaderExpr),
2747 If { cond: ShaderExpr, then: Vec<ShaderStmt>, else_: Option<Vec<ShaderStmt>> },
2748 For { init: Option<Box<ShaderStmt>>, cond: Option<ShaderExpr>, update: Option<ShaderExpr>, body: Vec<ShaderStmt> },
2749 While { cond: ShaderExpr, body: Vec<ShaderStmt> },
2750 Return(Option<ShaderExpr>),
2751 Break,
2752 Continue,
2753 Discard,
2754 Block(Vec<ShaderStmt>),
2755}
2756
2757#[derive(Clone, Debug)]
2760pub struct ShaderCodeGen {
2761 pub target: CodeGenTarget,
2762 pub indent_size: usize,
2763 pub use_precision_qualifiers: bool,
2764 pub emit_line_directives: bool,
2765}
2766
2767#[derive(Clone, Debug, PartialEq)]
2768pub enum CodeGenTarget { Glsl450, Glsl300Es, Wgsl, Hlsl50, Msl20 }
2769
2770impl ShaderCodeGen {
2771 pub fn new(target: CodeGenTarget) -> Self { Self { target, indent_size: 4, use_precision_qualifiers: false, emit_line_directives: false } }
2772 pub fn glsl450() -> Self { Self::new(CodeGenTarget::Glsl450) }
2773 pub fn wgsl() -> Self { Self::new(CodeGenTarget::Wgsl) }
2774 pub fn version_directive(&self) -> String {
2775 match self.target {
2776 CodeGenTarget::Glsl450 => "#version 450\n".into(),
2777 CodeGenTarget::Glsl300Es => "#version 300 es\nprecision highp float;\n".into(),
2778 CodeGenTarget::Wgsl => "// WGSL\n".into(),
2779 CodeGenTarget::Hlsl50 => "// HLSL 5.0\n".into(),
2780 CodeGenTarget::Msl20 => "#include <metal_stdlib>\nusing namespace metal;\n".into(),
2781 }
2782 }
2783 pub fn emit_uniform(&self, var: &ShaderVariable) -> String {
2784 match self.target {
2785 CodeGenTarget::Glsl450 | CodeGenTarget::Glsl300Es => {
2786 let layout = var.binding.map(|b| format!("layout(binding={}) ", b)).unwrap_or_default();
2787 format!("{}uniform {} {};", layout, var.data_type.glsl_name(), var.name)
2788 }
2789 _ => format!("// uniform {} {}", var.data_type.glsl_name(), var.name),
2790 }
2791 }
2792 pub fn emit_input(&self, var: &ShaderVariable) -> String {
2793 let loc = var.location.map(|l| format!("layout(location={}) ", l)).unwrap_or_default();
2794 format!("{}in {} {};", loc, var.data_type.glsl_name(), var.name)
2795 }
2796 pub fn emit_output(&self, var: &ShaderVariable) -> String {
2797 let loc = var.location.map(|l| format!("layout(location={}) ", l)).unwrap_or_default();
2798 format!("{}out {} {};", loc, var.data_type.glsl_name(), var.name)
2799 }
2800 pub fn emit_struct(&self, s: &ShaderStruct) -> String { s.generate_glsl() }
2801 pub fn emit_function_sig(&self, f: &ShaderFunction) -> String { f.signature() }
2802 pub fn is_glsl(&self) -> bool { matches!(self.target, CodeGenTarget::Glsl450 | CodeGenTarget::Glsl300Es) }
2803}
2804
2805impl Default for ShaderCodeGen {
2806 fn default() -> Self { Self::glsl450() }
2807}
2808
2809#[derive(Clone, Debug)]
2812pub struct ShaderOptimizer {
2813 pub level: OptimizationLevel,
2814 pub fold_constants: bool,
2815 pub dead_code_elimination: bool,
2816 pub inline_functions: bool,
2817 pub common_subexpr_elim: bool,
2818 pub loop_unroll_threshold: u32,
2819 pub vectorize: bool,
2820}
2821
2822#[derive(Clone, Debug, PartialEq)]
2823pub enum OptimizationLevel { None, Low, Medium, High, Aggressive }
2824
2825impl ShaderOptimizer {
2826 pub fn new(level: OptimizationLevel) -> Self {
2827 let (fold, dce, inline, cse, unroll, vec) = match level {
2828 OptimizationLevel::None => (false, false, false, false, 0, false),
2829 OptimizationLevel::Low => (true, true, false, false, 4, false),
2830 OptimizationLevel::Medium => (true, true, true, true, 8, false),
2831 OptimizationLevel::High => (true, true, true, true, 16, true),
2832 OptimizationLevel::Aggressive => (true, true, true, true, 64, true),
2833 };
2834 Self { level, fold_constants: fold, dead_code_elimination: dce, inline_functions: inline, common_subexpr_elim: cse, loop_unroll_threshold: unroll, vectorize: vec }
2835 }
2836 pub fn none() -> Self { Self::new(OptimizationLevel::None) }
2837 pub fn release() -> Self { Self::new(OptimizationLevel::High) }
2838 pub fn debug() -> Self { Self::new(OptimizationLevel::None) }
2839 pub fn any_optimization_enabled(&self) -> bool { self.level != OptimizationLevel::None }
2840}
2841
2842impl Default for ShaderOptimizer {
2843 fn default() -> Self { Self::new(OptimizationLevel::Medium) }
2844}
2845
2846#[derive(Clone, Debug)]
2849pub struct ShaderCacheEntry {
2850 pub source_hash: u64,
2851 pub defines_hash: u64,
2852 pub compiled_bytes: Vec<u8>,
2853 pub target: CodeGenTarget,
2854 pub created_at: u64,
2855 pub hit_count: u32,
2856}
2857
2858impl ShaderCacheEntry {
2859 pub fn new(source_hash: u64, defines_hash: u64, bytes: Vec<u8>, target: CodeGenTarget) -> Self {
2860 Self { source_hash, defines_hash, compiled_bytes: bytes, target, created_at: 0, hit_count: 0 }
2861 }
2862 pub fn is_stale(&self, source_hash: u64, defines_hash: u64) -> bool {
2863 self.source_hash != source_hash || self.defines_hash != defines_hash
2864 }
2865 pub fn record_hit(&mut self) { self.hit_count += 1; }
2866 pub fn size_bytes(&self) -> usize { self.compiled_bytes.len() }
2867}
2868
2869#[derive(Clone, Debug)]
2870pub struct ShaderCache {
2871 pub entries: HashMap<String, ShaderCacheEntry>,
2872 pub max_size: usize,
2873 pub total_hits: u64,
2874 pub total_misses: u64,
2875 pub eviction_policy: CacheEvictionPolicy,
2876}
2877
2878#[derive(Clone, Debug, PartialEq)]
2879pub enum CacheEvictionPolicy { Lru, Fifo, LeastHits }
2880
2881impl ShaderCache {
2882 pub fn new(max_size: usize) -> Self { Self { entries: HashMap::new(), max_size, total_hits: 0, total_misses: 0, eviction_policy: CacheEvictionPolicy::Lru } }
2883 pub fn get(&mut self, key: &str) -> Option<&ShaderCacheEntry> {
2884 if let Some(e) = self.entries.get_mut(key) { e.record_hit(); self.total_hits += 1; Some(e) } else { self.total_misses += 1; None }
2885 }
2886 pub fn insert(&mut self, key: String, entry: ShaderCacheEntry) {
2887 if self.entries.len() >= self.max_size { self.evict(); }
2888 self.entries.insert(key, entry);
2889 }
2890 fn evict(&mut self) {
2891 if let Some(key) = self.entries.keys().next().cloned() { self.entries.remove(&key); }
2892 }
2893 pub fn hit_rate(&self) -> f32 {
2894 let total = self.total_hits + self.total_misses;
2895 if total == 0 { 0.0 } else { self.total_hits as f32 / total as f32 }
2896 }
2897 pub fn clear(&mut self) { self.entries.clear(); }
2898 pub fn size(&self) -> usize { self.entries.len() }
2899 pub fn total_bytes(&self) -> usize { self.entries.values().map(|e| e.size_bytes()).sum() }
2900}
2901
2902impl Default for ShaderCache {
2903 fn default() -> Self { Self::new(SHADER_CACHE_SIZE) }
2904}
2905
2906#[derive(Clone, Debug)]
2909pub struct ShaderTemplate {
2910 pub id: u32,
2911 pub name: String,
2912 pub description: String,
2913 pub vertex_template: String,
2914 pub fragment_template: String,
2915 pub parameters: Vec<ShaderTemplateParam>,
2916 pub category: String,
2917 pub tags: Vec<String>,
2918 pub is_builtin: bool,
2919}
2920
2921#[derive(Clone, Debug)]
2922pub struct ShaderTemplateParam {
2923 pub name: String,
2924 pub param_type: TemplateParamType,
2925 pub default_value: String,
2926 pub description: String,
2927 pub required: bool,
2928}
2929
2930#[derive(Clone, Debug, PartialEq)]
2931pub enum TemplateParamType { Bool, Int, Float, String, Color, Texture, Vec2, Vec3, Vec4 }
2932
2933impl ShaderTemplate {
2934 pub fn new(id: u32, name: impl Into<String>) -> Self {
2935 Self { id, name: name.into(), description: String::new(), vertex_template: String::new(), fragment_template: String::new(), parameters: Vec::new(), category: "general".into(), tags: Vec::new(), is_builtin: false }
2936 }
2937 pub fn add_param(&mut self, param: ShaderTemplateParam) { self.parameters.push(param); }
2938 pub fn instantiate(&self, params: &HashMap<String, String>) -> (String, String) {
2939 let mut vert = self.vertex_template.clone();
2940 let mut frag = self.fragment_template.clone();
2941 for p in &self.parameters {
2942 let val = params.get(&p.name).unwrap_or(&p.default_value);
2943 let placeholder = format!("{{{{{}}}}}", p.name);
2944 vert = vert.replace(&placeholder, val);
2945 frag = frag.replace(&placeholder, val);
2946 }
2947 (vert, frag)
2948 }
2949 pub fn param_count(&self) -> usize { self.parameters.len() }
2950 pub fn required_params(&self) -> Vec<&ShaderTemplateParam> { self.parameters.iter().filter(|p| p.required).collect() }
2951}
2952
2953#[derive(Clone, Debug)]
2954pub struct ShaderTemplateLibrary {
2955 pub templates: HashMap<u32, ShaderTemplate>,
2956 pub next_id: u32,
2957 pub categories: HashSet<String>,
2958}
2959
2960impl ShaderTemplateLibrary {
2961 pub fn new() -> Self { Self { templates: HashMap::new(), next_id: 1, categories: HashSet::new() } }
2962 pub fn add(&mut self, mut t: ShaderTemplate) -> u32 {
2963 let id = self.next_id; self.next_id += 1;
2964 t.id = id;
2965 self.categories.insert(t.category.clone());
2966 self.templates.insert(id, t);
2967 id
2968 }
2969 pub fn get(&self, id: u32) -> Option<&ShaderTemplate> { self.templates.get(&id) }
2970 pub fn by_category(&self, cat: &str) -> Vec<&ShaderTemplate> { self.templates.values().filter(|t| t.category == cat).collect() }
2971 pub fn find_by_name(&self, name: &str) -> Option<&ShaderTemplate> { self.templates.values().find(|t| t.name == name) }
2972 pub fn count(&self) -> usize { self.templates.len() }
2973}
2974
2975impl Default for ShaderTemplateLibrary {
2976 fn default() -> Self { Self::new() }
2977}
2978
2979#[derive(Clone, Debug)]
2982pub struct ShaderFileWatcher {
2983 pub watched_files: HashMap<String, ShaderWatchEntry>,
2984 pub reload_queue: VecDeque<String>,
2985 pub auto_reload: bool,
2986 pub debounce_ms: u32,
2987}
2988
2989#[derive(Clone, Debug)]
2990pub struct ShaderWatchEntry {
2991 pub path: String,
2992 pub last_modified: u64,
2993 pub shader_ids: Vec<u32>,
2994 pub reload_count: u32,
2995}
2996
2997impl ShaderFileWatcher {
2998 pub fn new() -> Self { Self { watched_files: HashMap::new(), reload_queue: VecDeque::new(), auto_reload: true, debounce_ms: 500 } }
2999 pub fn watch(&mut self, path: impl Into<String>, shader_id: u32) {
3000 let p = path.into();
3001 self.watched_files.entry(p.clone()).or_insert(ShaderWatchEntry { path: p, last_modified: 0, shader_ids: Vec::new(), reload_count: 0 }).shader_ids.push(shader_id);
3002 }
3003 pub fn mark_modified(&mut self, path: &str) {
3004 if let Some(entry) = self.watched_files.get_mut(path) {
3005 entry.reload_count += 1;
3006 if !self.reload_queue.contains(&path.to_string()) { self.reload_queue.push_back(path.to_string()); }
3007 }
3008 }
3009 pub fn next_reload(&mut self) -> Option<String> { self.reload_queue.pop_front() }
3010 pub fn shader_ids_for(&self, path: &str) -> Vec<u32> { self.watched_files.get(path).map(|e| e.shader_ids.clone()).unwrap_or_default() }
3011 pub fn unwatch(&mut self, path: &str) { self.watched_files.remove(path); }
3012 pub fn watched_count(&self) -> usize { self.watched_files.len() }
3013}
3014
3015impl Default for ShaderFileWatcher {
3016 fn default() -> Self { Self::new() }
3017}
3018
3019#[derive(Clone, Debug)]
3022pub struct ShaderVariant {
3023 pub id: u32,
3024 pub base_program_id: u32,
3025 pub keywords: Vec<String>,
3026 pub compiled_id: Option<u32>,
3027 pub is_fallback: bool,
3028}
3029
3030impl ShaderVariant {
3031 pub fn new(id: u32, base_id: u32) -> Self { Self { id, base_program_id: base_id, keywords: Vec::new(), compiled_id: None, is_fallback: false } }
3032 pub fn with_keyword(mut self, kw: impl Into<String>) -> Self { self.keywords.push(kw.into()); self }
3033 pub fn matches_keywords(&self, keywords: &[&str]) -> bool { keywords.iter().all(|kw| self.keywords.iter().any(|k| k == kw)) }
3034 pub fn keyword_hash(&self) -> u64 {
3035 let mut sorted = self.keywords.clone(); sorted.sort();
3036 sorted.iter().fold(0u64, |h, k| h.wrapping_mul(31).wrapping_add(k.bytes().map(|b| b as u64).sum::<u64>()))
3037 }
3038}
3039
3040#[derive(Clone, Debug)]
3041pub struct ShaderVariantCollection {
3042 pub base_id: u32,
3043 pub variants: Vec<ShaderVariant>,
3044 pub fallback_id: Option<u32>,
3045 pub next_id: u32,
3046}
3047
3048impl ShaderVariantCollection {
3049 pub fn new(base_id: u32) -> Self { Self { base_id, variants: Vec::new(), fallback_id: None, next_id: 1 } }
3050 pub fn add_variant(&mut self, keywords: Vec<String>) -> u32 {
3051 let id = self.next_id; self.next_id += 1;
3052 let mut v = ShaderVariant::new(id, self.base_id);
3053 v.keywords = keywords;
3054 self.variants.push(v);
3055 id
3056 }
3057 pub fn find_best_match(&self, keywords: &[&str]) -> Option<&ShaderVariant> {
3058 let mut best: Option<&ShaderVariant> = None;
3059 let mut best_matches = 0;
3060 for v in &self.variants {
3061 let matches = keywords.iter().filter(|kw| v.keywords.iter().any(|k| k == *kw)).count();
3062 if matches > best_matches { best_matches = matches; best = Some(v); }
3063 }
3064 best.or_else(|| self.fallback_id.and_then(|id| self.variants.iter().find(|v| v.id == id)))
3065 }
3066 pub fn variant_count(&self) -> usize { self.variants.len() }
3067 pub fn set_fallback(&mut self, id: u32) { self.fallback_id = Some(id); }
3068}
3069
3070#[derive(Clone, Debug)]
3073pub struct ShaderIncludeResolver {
3074 pub virtual_fs: HashMap<String, String>,
3075 pub include_paths: Vec<String>,
3076 pub resolved_cache: HashMap<String, String>,
3077 pub max_size_bytes: usize,
3078}
3079
3080impl ShaderIncludeResolver {
3081 pub fn new() -> Self { Self { virtual_fs: HashMap::new(), include_paths: Vec::new(), resolved_cache: HashMap::new(), max_size_bytes: 1024 * 1024 } }
3082 pub fn register_virtual(&mut self, path: impl Into<String>, content: impl Into<String>) { self.virtual_fs.insert(path.into(), content.into()); }
3083 pub fn add_include_path(&mut self, path: impl Into<String>) { self.include_paths.push(path.into()); }
3084 pub fn resolve(&self, include_path: &str) -> Option<&str> {
3085 self.resolved_cache.get(include_path).map(|s| s.as_str()).or_else(|| self.virtual_fs.get(include_path).map(|s| s.as_str()))
3086 }
3087 pub fn cache_result(&mut self, include_path: String, content: String) { self.resolved_cache.insert(include_path, content); }
3088 pub fn clear_cache(&mut self) { self.resolved_cache.clear(); }
3089 pub fn virtual_file_count(&self) -> usize { self.virtual_fs.len() }
3090}
3091
3092impl Default for ShaderIncludeResolver {
3093 fn default() -> Self { Self::new() }
3094}
3095
3096pub const SHADER_VARIANT_MAX_KEYWORDS: usize = 16;
3099pub const SHADER_TEMPLATE_MAX_PARAMS: usize = 32;
3100pub const SHADER_WATCHER_DEBOUNCE_DEFAULT: u32 = 500;
3101pub const SHADER_OPTIMIZER_UNROLL_DEFAULT: u32 = 8;
3102pub const SHADER_AST_MAX_DEPTH: usize = 64;
3103pub const SHADER_MAX_VARIANTS_PER_PROGRAM: usize = 256;
3104pub const SHADER_INCLUDE_RESOLVER_CACHE_MAX: usize = 512;
3105pub const SHADER_CODEGEN_INDENT_DEFAULT: usize = 4;
3106pub const SHADER_DIAGNOSTIC_MAX: usize = 1000;
3107
3108pub fn shader_targets() -> &'static [&'static str] { &["glsl450", "glsl300es", "wgsl", "hlsl50", "msl20"] }
3109pub fn shader_target_count() -> usize { shader_targets().len() }
3110pub fn is_gpu_shader_target(target: &CodeGenTarget) -> bool { !matches!(target, CodeGenTarget::Wgsl) || true }
3111
3112
3113#[derive(Clone, Debug)]
3116pub struct Material {
3117 pub id: u32,
3118 pub name: String,
3119 pub shader_program_id: u32,
3120 pub textures: Vec<MaterialTexture>,
3121 pub properties: HashMap<String, MaterialProperty>,
3122 pub pipeline: PipelineState,
3123 pub render_queue: u32,
3124 pub cast_shadows: bool,
3125 pub receive_shadows: bool,
3126 pub instanced: bool,
3127 pub double_sided: bool,
3128}
3129
3130#[derive(Clone, Debug)]
3131pub struct MaterialTexture {
3132 pub name: String,
3133 pub texture_id: u32,
3134 pub slot: u32,
3135 pub sampler_type: SamplerType,
3136 pub uv_channel: u32,
3137}
3138
3139#[derive(Clone, Debug)]
3140pub enum MaterialProperty {
3141 Float(f32),
3142 Vec2([f32; 2]),
3143 Vec3([f32; 3]),
3144 Vec4([f32; 4]),
3145 Int(i32),
3146 Bool(bool),
3147 Color([f32; 4]),
3148}
3149
3150#[derive(Clone, Debug, PartialEq)]
3151pub enum SamplerType { Linear, Nearest, LinearMipmap, Trilinear, Anisotropic }
3152
3153impl Material {
3154 pub fn new(id: u32, name: impl Into<String>, shader_id: u32) -> Self {
3155 Self { id, name: name.into(), shader_program_id: shader_id, textures: Vec::new(), properties: HashMap::new(), pipeline: PipelineState::default(), render_queue: 2000, cast_shadows: true, receive_shadows: true, instanced: false, double_sided: false }
3156 }
3157 pub fn add_texture(&mut self, name: impl Into<String>, tex_id: u32, slot: u32) {
3158 self.textures.push(MaterialTexture { name: name.into(), texture_id: tex_id, slot, sampler_type: SamplerType::Trilinear, uv_channel: 0 });
3159 }
3160 pub fn set_float(&mut self, name: impl Into<String>, value: f32) { self.properties.insert(name.into(), MaterialProperty::Float(value)); }
3161 pub fn set_color(&mut self, name: impl Into<String>, r: f32, g: f32, b: f32, a: f32) { self.properties.insert(name.into(), MaterialProperty::Color([r, g, b, a])); }
3162 pub fn set_vec4(&mut self, name: impl Into<String>, v: [f32; 4]) { self.properties.insert(name.into(), MaterialProperty::Vec4(v)); }
3163 pub fn is_transparent(&self) -> bool { self.pipeline.is_transparent() }
3164 pub fn is_opaque(&self) -> bool { !self.is_transparent() }
3165 pub fn texture_count(&self) -> usize { self.textures.len() }
3166 pub fn property_count(&self) -> usize { self.properties.len() }
3167 pub fn make_transparent(&mut self) { self.pipeline = PipelineState::transparent(); self.render_queue = 3000; }
3168 pub fn make_additive(&mut self) { self.pipeline = PipelineState::additive(); self.render_queue = 3500; }
3169}
3170
3171#[derive(Clone, Debug)]
3172pub struct MaterialLibrary {
3173 pub materials: HashMap<u32, Material>,
3174 pub next_id: u32,
3175 pub default_material_id: u32,
3176}
3177
3178impl MaterialLibrary {
3179 pub fn new() -> Self { Self { materials: HashMap::new(), next_id: 1, default_material_id: 0 } }
3180 pub fn add(&mut self, mat: Material) -> u32 {
3181 let id = self.next_id; self.next_id += 1;
3182 self.materials.insert(id, mat);
3183 id
3184 }
3185 pub fn get(&self, id: u32) -> Option<&Material> { self.materials.get(&id) }
3186 pub fn get_mut(&mut self, id: u32) -> Option<&mut Material> { self.materials.get_mut(&id) }
3187 pub fn find_by_name(&self, name: &str) -> Option<&Material> { self.materials.values().find(|m| m.name == name) }
3188 pub fn opaque_materials(&self) -> Vec<&Material> { self.materials.values().filter(|m| m.is_opaque()).collect() }
3189 pub fn transparent_materials(&self) -> Vec<&Material> { self.materials.values().filter(|m| m.is_transparent()).collect() }
3190 pub fn count(&self) -> usize { self.materials.len() }
3191 pub fn remove(&mut self, id: u32) -> bool { self.materials.remove(&id).is_some() }
3192 pub fn set_default(&mut self, id: u32) { self.default_material_id = id; }
3193}
3194
3195impl Default for MaterialLibrary {
3196 fn default() -> Self { Self::new() }
3197}
3198
3199#[derive(Clone, Debug)]
3202pub struct RenderPass {
3203 pub id: u32,
3204 pub name: String,
3205 pub pass_type: RenderPassType,
3206 pub color_attachments: Vec<ColorAttachment>,
3207 pub depth_attachment: Option<DepthAttachment>,
3208 pub clear_color: [f32; 4],
3209 pub clear_depth: f32,
3210 pub clear_stencil: u8,
3211 pub viewport: (u32, u32, u32, u32),
3212 pub enabled: bool,
3213 pub order: u32,
3214}
3215
3216#[derive(Clone, Debug, PartialEq)]
3217pub enum RenderPassType { Opaque, Transparent, Shadow, PostProcess, Ui, Compute, Custom }
3218
3219#[derive(Clone, Debug)]
3220pub struct ColorAttachment {
3221 pub texture_id: u32,
3222 pub format: AttachmentFormat,
3223 pub load_op: LoadOp,
3224 pub store_op: StoreOp,
3225 pub mip_level: u32,
3226 pub layer: u32,
3227}
3228
3229#[derive(Clone, Debug)]
3230pub struct DepthAttachment {
3231 pub texture_id: u32,
3232 pub format: AttachmentFormat,
3233 pub load_op: LoadOp,
3234 pub store_op: StoreOp,
3235 pub read_only: bool,
3236}
3237
3238#[derive(Clone, Debug, PartialEq)]
3239pub enum AttachmentFormat { Rgba8, Rgba16F, Rgba32F, R8, R16F, R32F, Rg8, Rg16F, Depth16, Depth24, Depth32F, Depth24Stencil8 }
3240
3241#[derive(Clone, Debug, PartialEq)]
3242pub enum LoadOp { Load, Clear, DontCare }
3243#[derive(Clone, Debug, PartialEq)]
3244pub enum StoreOp { Store, DontCare }
3245
3246impl RenderPass {
3247 pub fn new(id: u32, name: impl Into<String>, pass_type: RenderPassType) -> Self {
3248 Self { id, name: name.into(), pass_type, color_attachments: Vec::new(), depth_attachment: None, clear_color: [0.0, 0.0, 0.0, 1.0], clear_depth: 1.0, clear_stencil: 0, viewport: (0, 0, 1920, 1080), enabled: true, order: 0 }
3249 }
3250 pub fn add_color_attachment(&mut self, tex_id: u32, format: AttachmentFormat) {
3251 self.color_attachments.push(ColorAttachment { texture_id: tex_id, format, load_op: LoadOp::Clear, store_op: StoreOp::Store, mip_level: 0, layer: 0 });
3252 }
3253 pub fn set_depth(&mut self, tex_id: u32) {
3254 self.depth_attachment = Some(DepthAttachment { texture_id: tex_id, format: AttachmentFormat::Depth32F, load_op: LoadOp::Clear, store_op: StoreOp::Store, read_only: false });
3255 }
3256 pub fn is_shadow_pass(&self) -> bool { self.pass_type == RenderPassType::Shadow }
3257 pub fn is_postprocess(&self) -> bool { self.pass_type == RenderPassType::PostProcess }
3258 pub fn attachment_count(&self) -> usize { self.color_attachments.len() + if self.depth_attachment.is_some() { 1 } else { 0 } }
3259 pub fn set_viewport(&mut self, x: u32, y: u32, w: u32, h: u32) { self.viewport = (x, y, w, h); }
3260}
3261
3262#[derive(Clone, Debug)]
3263pub struct RenderPipeline {
3264 pub passes: Vec<RenderPass>,
3265 pub next_id: u32,
3266}
3267
3268impl RenderPipeline {
3269 pub fn new() -> Self { Self { passes: Vec::new(), next_id: 1 } }
3270 pub fn add_pass(&mut self, pass: RenderPass) { self.passes.push(pass); }
3271 pub fn sorted_passes(&self) -> Vec<&RenderPass> {
3272 let mut sorted: Vec<_> = self.passes.iter().filter(|p| p.enabled).collect();
3273 sorted.sort_by_key(|p| p.order);
3274 sorted
3275 }
3276 pub fn pass_count(&self) -> usize { self.passes.len() }
3277 pub fn find_by_type(&self, pass_type: &RenderPassType) -> Vec<&RenderPass> {
3278 self.passes.iter().filter(|p| &p.pass_type == pass_type).collect()
3279 }
3280 pub fn enable_pass(&mut self, id: u32) { if let Some(p) = self.passes.iter_mut().find(|p| p.id == id) { p.enabled = true; } }
3281 pub fn disable_pass(&mut self, id: u32) { if let Some(p) = self.passes.iter_mut().find(|p| p.id == id) { p.enabled = false; } }
3282}
3283
3284impl Default for RenderPipeline {
3285 fn default() -> Self { Self::new() }
3286}
3287
3288#[derive(Clone, Debug)]
3291pub struct PostProcessEffect {
3292 pub id: u32,
3293 pub name: String,
3294 pub effect_type: PostEffectType,
3295 pub shader_program_id: u32,
3296 pub properties: HashMap<String, f32>,
3297 pub enabled: bool,
3298 pub order: u32,
3299 pub input_texture: u32,
3300 pub output_texture: u32,
3301}
3302
3303#[derive(Clone, Debug, PartialEq)]
3304pub enum PostEffectType { Bloom, Tonemap, Vignette, ChromaticAberration, DepthOfField, MotionBlur, Ssao, Fxaa, Custom }
3305
3306impl PostProcessEffect {
3307 pub fn new(id: u32, name: impl Into<String>, effect_type: PostEffectType, shader_id: u32) -> Self {
3308 Self { id, name: name.into(), effect_type, shader_program_id: shader_id, properties: HashMap::new(), enabled: true, order: 0, input_texture: 0, output_texture: 0 }
3309 }
3310 pub fn set_property(&mut self, key: impl Into<String>, val: f32) { self.properties.insert(key.into(), val); }
3311 pub fn get_property(&self, key: &str) -> f32 { self.properties.get(key).copied().unwrap_or(0.0) }
3312 pub fn bloom_intensity(&self) -> f32 { self.get_property("intensity") }
3313 pub fn vignette_strength(&self) -> f32 { self.get_property("strength") }
3314 pub fn is_bloom(&self) -> bool { self.effect_type == PostEffectType::Bloom }
3315 pub fn is_tonemap(&self) -> bool { self.effect_type == PostEffectType::Tonemap }
3316}
3317
3318#[derive(Clone, Debug)]
3319pub struct PostProcessStack {
3320 pub effects: Vec<PostProcessEffect>,
3321 pub enabled: bool,
3322}
3323
3324impl PostProcessStack {
3325 pub fn new() -> Self { Self { effects: Vec::new(), enabled: true } }
3326 pub fn add(&mut self, effect: PostProcessEffect) { self.effects.push(effect); }
3327 pub fn enabled_effects(&self) -> Vec<&PostProcessEffect> {
3328 let mut v: Vec<_> = self.effects.iter().filter(|e| e.enabled).collect();
3329 v.sort_by_key(|e| e.order);
3330 v
3331 }
3332 pub fn enable_effect(&mut self, id: u32) { if let Some(e) = self.effects.iter_mut().find(|e| e.id == id) { e.enabled = true; } }
3333 pub fn disable_effect(&mut self, id: u32) { if let Some(e) = self.effects.iter_mut().find(|e| e.id == id) { e.enabled = false; } }
3334 pub fn effect_count(&self) -> usize { self.effects.len() }
3335 pub fn active_count(&self) -> usize { self.effects.iter().filter(|e| e.enabled).count() }
3336}
3337
3338impl Default for PostProcessStack {
3339 fn default() -> Self { Self::new() }
3340}
3341
3342#[derive(Clone, Debug)]
3345pub struct ShaderNode {
3346 pub id: u32,
3347 pub node_type: ShaderNodeType,
3348 pub label: String,
3349 pub position: (f32, f32),
3350 pub inputs: Vec<ShaderNodePort>,
3351 pub outputs: Vec<ShaderNodePort>,
3352 pub properties: HashMap<String, String>,
3353}
3354
3355#[derive(Clone, Debug, PartialEq)]
3356pub enum ShaderNodeType {
3357 Input, Output, Math, Texture, Color, Constant, Mix, Clamp, Normalize, Length, Dot, Cross, Reflect, Refract, Fresnel, NormalMap, VertexShader, FragmentShader, Custom(String),
3358}
3359
3360#[derive(Clone, Debug)]
3361pub struct ShaderNodePort {
3362 pub id: u32,
3363 pub name: String,
3364 pub port_type: ShaderDataType,
3365 pub connected_to: Option<(u32, u32)>,
3366 pub default_value: String,
3367}
3368
3369impl ShaderNode {
3370 pub fn new(id: u32, node_type: ShaderNodeType, label: impl Into<String>) -> Self {
3371 Self { id, node_type, label: label.into(), position: (0.0, 0.0), inputs: Vec::new(), outputs: Vec::new(), properties: HashMap::new() }
3372 }
3373 pub fn add_input(&mut self, name: impl Into<String>, port_type: ShaderDataType) -> u32 {
3374 let pid = self.inputs.len() as u32;
3375 self.inputs.push(ShaderNodePort { id: pid, name: name.into(), port_type, connected_to: None, default_value: "0.0".into() });
3376 pid
3377 }
3378 pub fn add_output(&mut self, name: impl Into<String>, port_type: ShaderDataType) -> u32 {
3379 let pid = self.outputs.len() as u32;
3380 self.outputs.push(ShaderNodePort { id: pid, name: name.into(), port_type, connected_to: None, default_value: "0.0".into() });
3381 pid
3382 }
3383 pub fn set_property(&mut self, key: impl Into<String>, val: impl Into<String>) { self.properties.insert(key.into(), val.into()); }
3384 pub fn is_output_node(&self) -> bool { self.node_type == ShaderNodeType::Output }
3385 pub fn is_input_node(&self) -> bool { self.node_type == ShaderNodeType::Input }
3386 pub fn input_count(&self) -> usize { self.inputs.len() }
3387 pub fn output_count(&self) -> usize { self.outputs.len() }
3388}
3389
3390#[derive(Clone, Debug)]
3391pub struct ShaderNodeGraph {
3392 pub nodes: HashMap<u32, ShaderNode>,
3393 pub connections: Vec<(u32, u32, u32, u32)>,
3394 pub next_id: u32,
3395 pub name: String,
3396}
3397
3398impl ShaderNodeGraph {
3399 pub fn new(name: impl Into<String>) -> Self { Self { nodes: HashMap::new(), connections: Vec::new(), next_id: 1, name: name.into() } }
3400 pub fn add_node(&mut self, node_type: ShaderNodeType, label: impl Into<String>) -> u32 {
3401 let id = self.next_id; self.next_id += 1;
3402 self.nodes.insert(id, ShaderNode::new(id, node_type, label));
3403 id
3404 }
3405 pub fn connect(&mut self, from_node: u32, from_port: u32, to_node: u32, to_port: u32) {
3406 self.connections.push((from_node, from_port, to_node, to_port));
3407 if let Some(node) = self.nodes.get_mut(&to_node) {
3408 if let Some(port) = node.inputs.get_mut(to_port as usize) { port.connected_to = Some((from_node, from_port)); }
3409 }
3410 }
3411 pub fn disconnect(&mut self, to_node: u32, to_port: u32) {
3412 self.connections.retain(|(_, _, tn, tp)| !(*tn == to_node && *tp == to_port));
3413 if let Some(node) = self.nodes.get_mut(&to_node) {
3414 if let Some(port) = node.inputs.get_mut(to_port as usize) { port.connected_to = None; }
3415 }
3416 }
3417 pub fn remove_node(&mut self, id: u32) {
3418 self.nodes.remove(&id);
3419 self.connections.retain(|(a, _, b, _)| *a != id && *b != id);
3420 }
3421 pub fn node_count(&self) -> usize { self.nodes.len() }
3422 pub fn connection_count(&self) -> usize { self.connections.len() }
3423 pub fn output_node(&self) -> Option<&ShaderNode> { self.nodes.values().find(|n| n.is_output_node()) }
3424 pub fn move_node(&mut self, id: u32, x: f32, y: f32) { if let Some(n) = self.nodes.get_mut(&id) { n.position = (x, y); } }
3425}
3426
3427impl Default for ShaderNodeGraph {
3428 fn default() -> Self { Self::new("new_graph") }
3429}
3430
3431pub const MATERIAL_MAX_TEXTURES: usize = 16;
3434pub const MATERIAL_MAX_PROPERTIES: usize = 64;
3435pub const RENDER_PASS_MAX: usize = 32;
3436pub const POST_PROCESS_EFFECT_MAX: usize = 16;
3437pub const SHADER_NODE_GRAPH_MAX_NODES: usize = 512;
3438pub const SHADER_NODE_GRAPH_MAX_CONNECTIONS: usize = 1024;
3439pub const MATERIAL_OPAQUE_QUEUE: u32 = 2000;
3440pub const MATERIAL_TRANSPARENT_QUEUE: u32 = 3000;
3441pub const MATERIAL_OVERLAY_QUEUE: u32 = 4000;
3442
3443pub fn attachment_format_name(fmt: &AttachmentFormat) -> &'static str {
3444 match fmt {
3445 AttachmentFormat::Rgba8 => "RGBA8", AttachmentFormat::Rgba16F => "RGBA16F",
3446 AttachmentFormat::Rgba32F => "RGBA32F", AttachmentFormat::Depth32F => "Depth32F",
3447 AttachmentFormat::Depth24Stencil8 => "Depth24Stencil8", _ => "Unknown",
3448 }
3449}
3450pub fn blend_factor_glsl(f: &BlendFactor) -> &'static str {
3451 match f {
3452 BlendFactor::Zero => "GL_ZERO", BlendFactor::One => "GL_ONE",
3453 BlendFactor::SrcAlpha => "GL_SRC_ALPHA", BlendFactor::OneMinusSrcAlpha => "GL_ONE_MINUS_SRC_ALPHA",
3454 _ => "GL_ONE",
3455 }
3456}
3457
3458
3459#[derive(Clone, Debug)]
3462pub struct TextureDescriptor {
3463 pub id: u32,
3464 pub name: String,
3465 pub width: u32,
3466 pub height: u32,
3467 pub depth: u32,
3468 pub mip_levels: u32,
3469 pub array_layers: u32,
3470 pub format: AttachmentFormat,
3471 pub tex_type: TextureType,
3472 pub usage: TextureUsage,
3473 pub filter: TextureFilter,
3474 pub wrap_u: WrapMode,
3475 pub wrap_v: WrapMode,
3476 pub wrap_w: WrapMode,
3477 pub anisotropy: u32,
3478 pub generate_mipmaps: bool,
3479 pub srgb: bool,
3480 pub compressed: bool,
3481}
3482
3483#[derive(Clone, Debug, PartialEq)]
3484pub enum TextureType { Tex2D, Tex3D, TexCube, Tex2DArray, TexCubeArray }
3485#[derive(Clone, Debug, PartialEq)]
3486pub enum TextureUsage { Sampled, Storage, RenderTarget, DepthStencil }
3487#[derive(Clone, Debug, PartialEq)]
3488pub enum TextureFilter { Nearest, Linear, Trilinear, Anisotropic }
3489#[derive(Clone, Debug, PartialEq)]
3490pub enum WrapMode { Repeat, MirroredRepeat, ClampToEdge, ClampToBorder, MirrorClampToEdge }
3491
3492impl TextureDescriptor {
3493 pub fn new_2d(id: u32, name: impl Into<String>, width: u32, height: u32, format: AttachmentFormat) -> Self {
3494 Self { id, name: name.into(), width, height, depth: 1, mip_levels: 1, array_layers: 1, format, tex_type: TextureType::Tex2D, usage: TextureUsage::Sampled, filter: TextureFilter::Trilinear, wrap_u: WrapMode::Repeat, wrap_v: WrapMode::Repeat, wrap_w: WrapMode::ClampToEdge, anisotropy: 4, generate_mipmaps: true, srgb: false, compressed: false }
3495 }
3496 pub fn render_target(id: u32, name: impl Into<String>, w: u32, h: u32, fmt: AttachmentFormat) -> Self {
3497 let mut t = Self::new_2d(id, name, w, h, fmt); t.usage = TextureUsage::RenderTarget; t.generate_mipmaps = false; t
3498 }
3499 pub fn depth_target(id: u32, name: impl Into<String>, w: u32, h: u32) -> Self {
3500 let mut t = Self::new_2d(id, name, w, h, AttachmentFormat::Depth32F); t.usage = TextureUsage::DepthStencil; t.generate_mipmaps = false; t
3501 }
3502 pub fn mip_count_for_size(width: u32, height: u32) -> u32 { (width.max(height) as f32).log2().floor() as u32 + 1 }
3503 pub fn bytes_per_pixel(&self) -> u32 {
3504 match self.format {
3505 AttachmentFormat::Rgba8 => 4, AttachmentFormat::Rgba16F => 8, AttachmentFormat::Rgba32F => 16,
3506 AttachmentFormat::R8 => 1, AttachmentFormat::R16F => 2, AttachmentFormat::R32F => 4,
3507 AttachmentFormat::Rg8 => 2, AttachmentFormat::Rg16F => 4,
3508 AttachmentFormat::Depth32F => 4, _ => 4,
3509 }
3510 }
3511 pub fn total_bytes(&self) -> u64 { self.width as u64 * self.height as u64 * self.depth as u64 * self.bytes_per_pixel() as u64 }
3512 pub fn is_hdr(&self) -> bool { matches!(self.format, AttachmentFormat::Rgba16F | AttachmentFormat::Rgba32F | AttachmentFormat::R16F | AttachmentFormat::R32F) }
3513 pub fn is_cubemap(&self) -> bool { self.tex_type == TextureType::TexCube }
3514 pub fn is_depth(&self) -> bool { matches!(self.format, AttachmentFormat::Depth16 | AttachmentFormat::Depth24 | AttachmentFormat::Depth32F | AttachmentFormat::Depth24Stencil8) }
3515 pub fn aspect_ratio(&self) -> f32 { if self.height == 0 { 1.0 } else { self.width as f32 / self.height as f32 } }
3516}
3517
3518#[derive(Clone, Debug)]
3519pub struct TextureRegistry {
3520 pub textures: HashMap<u32, TextureDescriptor>,
3521 pub next_id: u32,
3522}
3523
3524impl TextureRegistry {
3525 pub fn new() -> Self { Self { textures: HashMap::new(), next_id: 1 } }
3526 pub fn register(&mut self, mut tex: TextureDescriptor) -> u32 {
3527 let id = self.next_id; self.next_id += 1;
3528 tex.id = id;
3529 self.textures.insert(id, tex);
3530 id
3531 }
3532 pub fn get(&self, id: u32) -> Option<&TextureDescriptor> { self.textures.get(&id) }
3533 pub fn find_by_name(&self, name: &str) -> Option<&TextureDescriptor> { self.textures.values().find(|t| t.name == name) }
3534 pub fn total_bytes(&self) -> u64 { self.textures.values().map(|t| t.total_bytes()).sum() }
3535 pub fn count(&self) -> usize { self.textures.len() }
3536 pub fn hdr_textures(&self) -> Vec<&TextureDescriptor> { self.textures.values().filter(|t| t.is_hdr()).collect() }
3537 pub fn depth_textures(&self) -> Vec<&TextureDescriptor> { self.textures.values().filter(|t| t.is_depth()).collect() }
3538 pub fn remove(&mut self, id: u32) -> bool { self.textures.remove(&id).is_some() }
3539}
3540
3541impl Default for TextureRegistry {
3542 fn default() -> Self { Self::new() }
3543}
3544
3545#[derive(Clone, Debug)]
3548pub struct SamplerDescriptor {
3549 pub id: u32,
3550 pub min_filter: TextureFilter,
3551 pub mag_filter: TextureFilter,
3552 pub mip_filter: TextureFilter,
3553 pub wrap_u: WrapMode,
3554 pub wrap_v: WrapMode,
3555 pub wrap_w: WrapMode,
3556 pub anisotropy: u32,
3557 pub border_color: [f32; 4],
3558 pub lod_min: f32,
3559 pub lod_max: f32,
3560 pub lod_bias: f32,
3561 pub compare_op: Option<CompareFunc>,
3562}
3563
3564impl SamplerDescriptor {
3565 pub fn default_linear() -> Self {
3566 Self { id: 0, min_filter: TextureFilter::Linear, mag_filter: TextureFilter::Linear, mip_filter: TextureFilter::Linear, wrap_u: WrapMode::Repeat, wrap_v: WrapMode::Repeat, wrap_w: WrapMode::Repeat, anisotropy: 1, border_color: [0.0; 4], lod_min: 0.0, lod_max: 1000.0, lod_bias: 0.0, compare_op: None }
3567 }
3568 pub fn default_nearest() -> Self { Self { min_filter: TextureFilter::Nearest, mag_filter: TextureFilter::Nearest, mip_filter: TextureFilter::Nearest, ..Self::default_linear() } }
3569 pub fn shadow() -> Self { Self { compare_op: Some(CompareFunc::Less), ..Self::default_linear() } }
3570 pub fn clamp_to_edge() -> Self { Self { wrap_u: WrapMode::ClampToEdge, wrap_v: WrapMode::ClampToEdge, wrap_w: WrapMode::ClampToEdge, ..Self::default_linear() } }
3571 pub fn anisotropic_16() -> Self { Self { anisotropy: 16, ..Self::default_linear() } }
3572 pub fn is_shadow_sampler(&self) -> bool { self.compare_op.is_some() }
3573 pub fn is_anisotropic(&self) -> bool { self.anisotropy > 1 }
3574}
3575
3576#[derive(Clone, Debug)]
3579pub struct VertexAttribute {
3580 pub location: u32,
3581 pub name: String,
3582 pub format: VertexAttribFormat,
3583 pub offset: u32,
3584 pub normalized: bool,
3585}
3586
3587#[derive(Clone, Debug, PartialEq, Copy)]
3588pub enum VertexAttribFormat {
3589 Float1, Float2, Float3, Float4,
3590 Sint1, Sint2, Sint3, Sint4,
3591 Uint1, Uint2, Uint3, Uint4,
3592 Unorm4, Snorm4,
3593 Half2, Half4,
3594}
3595
3596impl VertexAttribFormat {
3597 pub fn size_bytes(self) -> u32 {
3598 match self {
3599 VertexAttribFormat::Float1 | VertexAttribFormat::Sint1 | VertexAttribFormat::Uint1 => 4,
3600 VertexAttribFormat::Float2 | VertexAttribFormat::Sint2 | VertexAttribFormat::Uint2 | VertexAttribFormat::Half4 => 8,
3601 VertexAttribFormat::Float3 | VertexAttribFormat::Sint3 | VertexAttribFormat::Uint3 => 12,
3602 VertexAttribFormat::Float4 | VertexAttribFormat::Sint4 | VertexAttribFormat::Uint4 | VertexAttribFormat::Unorm4 | VertexAttribFormat::Snorm4 => 16,
3603 VertexAttribFormat::Half2 => 4,
3604 }
3605 }
3606 pub fn component_count(self) -> u32 {
3607 match self {
3608 VertexAttribFormat::Float1 | VertexAttribFormat::Sint1 | VertexAttribFormat::Uint1 => 1,
3609 VertexAttribFormat::Float2 | VertexAttribFormat::Sint2 | VertexAttribFormat::Uint2 | VertexAttribFormat::Half2 => 2,
3610 VertexAttribFormat::Float3 | VertexAttribFormat::Sint3 | VertexAttribFormat::Uint3 => 3,
3611 _ => 4,
3612 }
3613 }
3614 pub fn glsl_type_name(self) -> &'static str {
3615 match self {
3616 VertexAttribFormat::Float1 => "float", VertexAttribFormat::Float2 => "vec2",
3617 VertexAttribFormat::Float3 => "vec3", VertexAttribFormat::Float4 => "vec4",
3618 VertexAttribFormat::Sint1 => "int", VertexAttribFormat::Sint2 => "ivec2",
3619 VertexAttribFormat::Sint3 => "ivec3", VertexAttribFormat::Sint4 => "ivec4",
3620 VertexAttribFormat::Uint1 => "uint", VertexAttribFormat::Uint2 => "uvec2",
3621 VertexAttribFormat::Uint3 => "uvec3", VertexAttribFormat::Uint4 => "uvec4",
3622 _ => "vec4",
3623 }
3624 }
3625}
3626
3627#[derive(Clone, Debug)]
3628pub struct VertexLayout {
3629 pub attributes: Vec<VertexAttribute>,
3630 pub stride: u32,
3631 pub instanced: bool,
3632 pub instance_rate: u32,
3633}
3634
3635impl VertexLayout {
3636 pub fn new() -> Self { Self { attributes: Vec::new(), stride: 0, instanced: false, instance_rate: 1 } }
3637 pub fn add_attribute(&mut self, location: u32, name: impl Into<String>, format: VertexAttribFormat) {
3638 let offset = self.stride;
3639 self.stride += format.size_bytes();
3640 self.attributes.push(VertexAttribute { location, name: name.into(), format, offset, normalized: false });
3641 }
3642 pub fn standard_mesh() -> Self {
3643 let mut layout = Self::new();
3644 layout.add_attribute(0, "position", VertexAttribFormat::Float3);
3645 layout.add_attribute(1, "normal", VertexAttribFormat::Float3);
3646 layout.add_attribute(2, "uv", VertexAttribFormat::Float2);
3647 layout.add_attribute(3, "tangent", VertexAttribFormat::Float4);
3648 layout
3649 }
3650 pub fn position_only() -> Self {
3651 let mut layout = Self::new();
3652 layout.add_attribute(0, "position", VertexAttribFormat::Float3);
3653 layout
3654 }
3655 pub fn particle() -> Self {
3656 let mut layout = Self::new();
3657 layout.add_attribute(0, "position", VertexAttribFormat::Float3);
3658 layout.add_attribute(1, "color", VertexAttribFormat::Unorm4);
3659 layout.add_attribute(2, "size_rotation", VertexAttribFormat::Float2);
3660 layout
3661 }
3662 pub fn attribute_count(&self) -> usize { self.attributes.len() }
3663 pub fn generate_glsl_inputs(&self) -> String {
3664 self.attributes.iter().map(|a| format!("layout(location={}) in {} {};\n", a.location, a.format.glsl_type_name(), a.name)).collect()
3665 }
3666}
3667
3668impl Default for VertexLayout {
3669 fn default() -> Self { Self::standard_mesh() }
3670}
3671
3672#[derive(Clone, Debug)]
3675pub struct GpuBuffer {
3676 pub id: u32,
3677 pub name: String,
3678 pub size_bytes: u64,
3679 pub usage: BufferUsage,
3680 pub access: BufferAccess,
3681 pub binding: Option<u32>,
3682 pub stride: u32,
3683 pub element_count: u32,
3684}
3685
3686#[derive(Clone, Debug, PartialEq)]
3687pub enum BufferUsage { Vertex, Index, Uniform, Storage, Indirect, Staging }
3688#[derive(Clone, Debug, PartialEq)]
3689pub enum BufferAccess { GpuOnly, CpuToGpu, GpuToCpu, CpuOnly }
3690
3691impl GpuBuffer {
3692 pub fn new(id: u32, name: impl Into<String>, size: u64, usage: BufferUsage) -> Self {
3693 Self { id, name: name.into(), size_bytes: size, usage, access: BufferAccess::GpuOnly, binding: None, stride: 0, element_count: 0 }
3694 }
3695 pub fn vertex(id: u32, name: impl Into<String>, vertex_count: u32, stride: u32) -> Self {
3696 let mut b = Self::new(id, name, vertex_count as u64 * stride as u64, BufferUsage::Vertex);
3697 b.stride = stride; b.element_count = vertex_count; b
3698 }
3699 pub fn index(id: u32, name: impl Into<String>, index_count: u32, is_u32: bool) -> Self {
3700 let stride = if is_u32 { 4 } else { 2 };
3701 let mut b = Self::new(id, name, index_count as u64 * stride, BufferUsage::Index);
3702 b.stride = stride as u32; b.element_count = index_count; b
3703 }
3704 pub fn uniform(id: u32, name: impl Into<String>, size: u64, binding: u32) -> Self {
3705 let mut b = Self::new(id, name, size, BufferUsage::Uniform);
3706 b.binding = Some(binding); b
3707 }
3708 pub fn is_vertex_buffer(&self) -> bool { self.usage == BufferUsage::Vertex }
3709 pub fn is_index_buffer(&self) -> bool { self.usage == BufferUsage::Index }
3710 pub fn is_uniform_buffer(&self) -> bool { self.usage == BufferUsage::Uniform }
3711 pub fn size_kb(&self) -> f64 { self.size_bytes as f64 / 1024.0 }
3712 pub fn size_mb(&self) -> f64 { self.size_bytes as f64 / (1024.0 * 1024.0) }
3713}
3714
3715#[derive(Clone, Debug)]
3716pub struct BufferRegistry {
3717 pub buffers: HashMap<u32, GpuBuffer>,
3718 pub next_id: u32,
3719 pub total_bytes: u64,
3720}
3721
3722impl BufferRegistry {
3723 pub fn new() -> Self { Self { buffers: HashMap::new(), next_id: 1, total_bytes: 0 } }
3724 pub fn register(&mut self, mut buf: GpuBuffer) -> u32 {
3725 let id = self.next_id; self.next_id += 1;
3726 buf.id = id;
3727 self.total_bytes += buf.size_bytes;
3728 self.buffers.insert(id, buf);
3729 id
3730 }
3731 pub fn get(&self, id: u32) -> Option<&GpuBuffer> { self.buffers.get(&id) }
3732 pub fn remove(&mut self, id: u32) -> bool {
3733 if let Some(b) = self.buffers.remove(&id) { self.total_bytes = self.total_bytes.saturating_sub(b.size_bytes); true } else { false }
3734 }
3735 pub fn by_usage(&self, usage: &BufferUsage) -> Vec<&GpuBuffer> { self.buffers.values().filter(|b| &b.usage == usage).collect() }
3736 pub fn total_mb(&self) -> f64 { self.total_bytes as f64 / (1024.0 * 1024.0) }
3737 pub fn count(&self) -> usize { self.buffers.len() }
3738}
3739
3740impl Default for BufferRegistry {
3741 fn default() -> Self { Self::new() }
3742}
3743
3744#[derive(Clone, Debug)]
3747pub struct ComputePass {
3748 pub id: u32,
3749 pub name: String,
3750 pub shader_program_id: u32,
3751 pub dispatch_x: u32,
3752 pub dispatch_y: u32,
3753 pub dispatch_z: u32,
3754 pub bindings: Vec<ComputeBinding>,
3755 pub push_constants: Vec<u8>,
3756 pub enabled: bool,
3757}
3758
3759#[derive(Clone, Debug)]
3760pub struct ComputeBinding {
3761 pub set: u32,
3762 pub binding: u32,
3763 pub resource_type: ComputeResourceType,
3764 pub resource_id: u32,
3765}
3766
3767#[derive(Clone, Debug, PartialEq)]
3768pub enum ComputeResourceType { Texture, StorageTexture, Buffer, StorageBuffer, UniformBuffer }
3769
3770impl ComputePass {
3771 pub fn new(id: u32, name: impl Into<String>, shader_id: u32) -> Self {
3772 Self { id, name: name.into(), shader_program_id: shader_id, dispatch_x: 1, dispatch_y: 1, dispatch_z: 1, bindings: Vec::new(), push_constants: Vec::new(), enabled: true }
3773 }
3774 pub fn dispatch(mut self, x: u32, y: u32, z: u32) -> Self { self.dispatch_x = x; self.dispatch_y = y; self.dispatch_z = z; self }
3775 pub fn add_binding(&mut self, set: u32, binding: u32, res_type: ComputeResourceType, res_id: u32) {
3776 self.bindings.push(ComputeBinding { set, binding, resource_type: res_type, resource_id: res_id });
3777 }
3778 pub fn total_invocations(&self) -> u64 { self.dispatch_x as u64 * self.dispatch_y as u64 * self.dispatch_z as u64 }
3779 pub fn binding_count(&self) -> usize { self.bindings.len() }
3780}
3781
3782#[derive(Clone, Debug)]
3785pub struct GpuMemoryBudget {
3786 pub total_bytes: u64,
3787 pub available_bytes: u64,
3788 pub textures_bytes: u64,
3789 pub buffers_bytes: u64,
3790 pub render_targets_bytes: u64,
3791 pub shader_bytes: u64,
3792 pub misc_bytes: u64,
3793}
3794
3795impl GpuMemoryBudget {
3796 pub fn new(total: u64) -> Self {
3797 Self { total_bytes: total, available_bytes: total, textures_bytes: 0, buffers_bytes: 0, render_targets_bytes: 0, shader_bytes: 0, misc_bytes: 0 }
3798 }
3799 pub fn used_bytes(&self) -> u64 { self.textures_bytes + self.buffers_bytes + self.render_targets_bytes + self.shader_bytes + self.misc_bytes }
3800 pub fn usage_percent(&self) -> f32 { if self.total_bytes == 0 { 0.0 } else { self.used_bytes() as f32 / self.total_bytes as f32 * 100.0 } }
3801 pub fn is_overbudget(&self) -> bool { self.used_bytes() > self.total_bytes }
3802 pub fn headroom_mb(&self) -> f64 { (self.total_bytes.saturating_sub(self.used_bytes())) as f64 / (1024.0 * 1024.0) }
3803 pub fn allocate_texture(&mut self, bytes: u64) -> bool { if self.available_bytes >= bytes { self.textures_bytes += bytes; self.available_bytes -= bytes; true } else { false } }
3804 pub fn allocate_buffer(&mut self, bytes: u64) -> bool { if self.available_bytes >= bytes { self.buffers_bytes += bytes; self.available_bytes -= bytes; true } else { false } }
3805 pub fn free_texture(&mut self, bytes: u64) { self.textures_bytes = self.textures_bytes.saturating_sub(bytes); self.available_bytes += bytes.min(self.total_bytes); }
3806}
3807
3808#[derive(Clone, Debug, Default)]
3811pub struct RenderStatistics {
3812 pub draw_calls: u32,
3813 pub triangles: u64,
3814 pub vertices: u64,
3815 pub texture_switches: u32,
3816 pub shader_switches: u32,
3817 pub render_pass_count: u32,
3818 pub compute_dispatches: u32,
3819 pub gpu_time_ms: f32,
3820 pub cpu_time_ms: f32,
3821 pub frame_number: u64,
3822}
3823
3824impl RenderStatistics {
3825 pub fn new() -> Self { Self::default() }
3826 pub fn begin_frame(&mut self) {
3827 self.draw_calls = 0; self.triangles = 0; self.vertices = 0;
3828 self.texture_switches = 0; self.shader_switches = 0;
3829 self.render_pass_count = 0; self.compute_dispatches = 0;
3830 self.gpu_time_ms = 0.0; self.cpu_time_ms = 0.0;
3831 self.frame_number += 1;
3832 }
3833 pub fn record_draw(&mut self, vertices: u64, triangles: u64) { self.draw_calls += 1; self.vertices += vertices; self.triangles += triangles; }
3834 pub fn fps(&self) -> f32 { if self.gpu_time_ms < 1e-6 { 0.0 } else { 1000.0 / self.gpu_time_ms } }
3835 pub fn avg_tris_per_draw(&self) -> f64 { if self.draw_calls == 0 { 0.0 } else { self.triangles as f64 / self.draw_calls as f64 } }
3836 pub fn total_ms(&self) -> f32 { self.gpu_time_ms + self.cpu_time_ms }
3837}
3838
3839pub const TEXTURE_MAX_SIZE: u32 = 16384;
3842pub const TEXTURE_MAX_MIP_LEVELS: u32 = 15;
3843pub const TEXTURE_MAX_ARRAY_LAYERS: u32 = 2048;
3844pub const SAMPLER_MAX_ANISOTROPY: u32 = 16;
3845pub const BUFFER_ALIGNMENT: u32 = 256;
3846pub const VERTEX_LAYOUT_MAX_ATTRIBUTES: usize = 16;
3847pub const COMPUTE_MAX_WORKGROUP_SIZE: u32 = 1024;
3848pub const RENDER_PASS_MAX_COLOR_ATTACHMENTS: usize = 8;
3849pub const GPU_MEMORY_BUDGET_DEFAULT_MB: u64 = 4096;
3850pub const MATERIAL_DEFAULT_RENDER_QUEUE: u32 = 2000;
3851
3852pub fn texture_format_name(fmt: &AttachmentFormat) -> &'static str { attachment_format_name(fmt) }
3853pub fn vertex_format_size(fmt: VertexAttribFormat) -> u32 { fmt.size_bytes() }
3854pub fn buffer_aligned_size(size: u64) -> u64 { (size + BUFFER_ALIGNMENT as u64 - 1) & !(BUFFER_ALIGNMENT as u64 - 1) }
3855pub fn is_power_of_two(n: u32) -> bool { n > 0 && (n & (n - 1)) == 0 }
3856pub fn next_power_of_two(n: u32) -> u32 { if n == 0 { 1 } else { let mut p = n; p -= 1; p |= p >> 1; p |= p >> 2; p |= p >> 4; p |= p >> 8; p |= p >> 16; p + 1 } }
3857pub fn mip_count(width: u32, height: u32) -> u32 { TextureDescriptor::mip_count_for_size(width, height) }
3858pub fn shader_compiler_full_info() -> String {
3859 format!("ShaderCompiler — {} target platforms, materials, textures, buffers, render pipeline", shader_target_count())
3860}
3861
3862
3863#[derive(Clone, Debug)]
3866pub struct LightingModel {
3867 pub name: String,
3868 pub model_type: LightingModelType,
3869 pub diffuse_weight: f32,
3870 pub specular_weight: f32,
3871 pub ambient_weight: f32,
3872 pub subsurface_weight: f32,
3873 pub emission_weight: f32,
3874 pub roughness: f32,
3875 pub metalness: f32,
3876 pub ior: f32,
3877 pub transmission: f32,
3878 pub anisotropy: f32,
3879 pub clearcoat: f32,
3880 pub sheen: f32,
3881}
3882
3883#[derive(Clone, Debug, PartialEq)]
3884pub enum LightingModelType { Phong, BlinnPhong, Pbr, Lambert, Toon, Unlit, Custom(String) }
3885
3886impl LightingModel {
3887 pub fn phong() -> Self { Self { name: "Phong".into(), model_type: LightingModelType::Phong, diffuse_weight: 1.0, specular_weight: 0.5, ambient_weight: 0.1, subsurface_weight: 0.0, emission_weight: 0.0, roughness: 0.5, metalness: 0.0, ior: 1.5, transmission: 0.0, anisotropy: 0.0, clearcoat: 0.0, sheen: 0.0 } }
3888 pub fn pbr() -> Self { Self { name: "PBR".into(), model_type: LightingModelType::Pbr, diffuse_weight: 1.0, specular_weight: 1.0, ambient_weight: 0.1, subsurface_weight: 0.0, emission_weight: 0.0, roughness: 0.5, metalness: 0.0, ior: 1.5, transmission: 0.0, anisotropy: 0.0, clearcoat: 0.0, sheen: 0.0 } }
3889 pub fn unlit() -> Self { Self { name: "Unlit".into(), model_type: LightingModelType::Unlit, diffuse_weight: 0.0, specular_weight: 0.0, ambient_weight: 0.0, subsurface_weight: 0.0, emission_weight: 1.0, roughness: 1.0, metalness: 0.0, ior: 1.0, transmission: 0.0, anisotropy: 0.0, clearcoat: 0.0, sheen: 0.0 } }
3890 pub fn toon() -> Self { Self { name: "Toon".into(), model_type: LightingModelType::Toon, diffuse_weight: 1.0, specular_weight: 1.0, ambient_weight: 0.2, subsurface_weight: 0.0, emission_weight: 0.0, roughness: 1.0, metalness: 0.0, ior: 1.5, transmission: 0.0, anisotropy: 0.0, clearcoat: 0.0, sheen: 0.0 } }
3891 pub fn is_pbr(&self) -> bool { self.model_type == LightingModelType::Pbr }
3892 pub fn is_unlit(&self) -> bool { self.model_type == LightingModelType::Unlit }
3893 pub fn is_toon(&self) -> bool { self.model_type == LightingModelType::Toon }
3894 pub fn uses_ibl(&self) -> bool { self.is_pbr() }
3895 pub fn glsl_function_name(&self) -> &str {
3896 match self.model_type { LightingModelType::Phong => "shade_phong", LightingModelType::BlinnPhong => "shade_blinn_phong", LightingModelType::Pbr => "shade_pbr", LightingModelType::Lambert => "shade_lambert", LightingModelType::Toon => "shade_toon", LightingModelType::Unlit => "shade_unlit", _ => "shade_custom" }
3897 }
3898}
3899
3900impl Default for LightingModel {
3901 fn default() -> Self { Self::pbr() }
3902}
3903
3904pub struct GlslSnippet {
3907 pub name: &'static str,
3908 pub code: &'static str,
3909 pub dependencies: &'static [&'static str],
3910}
3911
3912pub const SNIPPET_GAMMA_CORRECT: GlslSnippet = GlslSnippet {
3913 name: "gamma_correct",
3914 code: "vec3 gamma_correct(vec3 color) { return pow(clamp(color, 0.0, 1.0), vec3(1.0 / 2.2)); }",
3915 dependencies: &[],
3916};
3917
3918pub const SNIPPET_LINEAR_TO_SRGB: GlslSnippet = GlslSnippet {
3919 name: "linear_to_srgb",
3920 code: "vec3 linear_to_srgb(vec3 c) { return mix(c * 12.92, 1.055 * pow(c, vec3(1.0/2.4)) - 0.055, step(vec3(0.0031308), c)); }",
3921 dependencies: &[],
3922};
3923
3924pub const SNIPPET_SRGB_TO_LINEAR: GlslSnippet = GlslSnippet {
3925 name: "srgb_to_linear",
3926 code: "vec3 srgb_to_linear(vec3 c) { return mix(c / 12.92, pow((c + 0.055) / 1.055, vec3(2.4)), step(vec3(0.04045), c)); }",
3927 dependencies: &[],
3928};
3929
3930pub const SNIPPET_ACES_TONEMAP: GlslSnippet = GlslSnippet {
3931 name: "aces_tonemap",
3932 code: "vec3 aces_tonemap(vec3 x) { float a=2.51; float b=0.03; float c=2.43; float d=0.59; float e=0.14; return clamp((x*(a*x+b))/(x*(c*x+d)+e),0.0,1.0); }",
3933 dependencies: &[],
3934};
3935
3936pub const SNIPPET_REINHARD_TONEMAP: GlslSnippet = GlslSnippet {
3937 name: "reinhard",
3938 code: "vec3 reinhard(vec3 c) { return c / (c + vec3(1.0)); }",
3939 dependencies: &[],
3940};
3941
3942pub const SNIPPET_FRESNEL: GlslSnippet = GlslSnippet {
3943 name: "fresnel_schlick",
3944 code: "vec3 fresnel_schlick(float cosTheta, vec3 F0) { return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0); }",
3945 dependencies: &[],
3946};
3947
3948pub const SNIPPET_NDF_GGX: GlslSnippet = GlslSnippet {
3949 name: "ndf_ggx",
3950 code: "float ndf_ggx(vec3 N, vec3 H, float roughness) { float a = roughness*roughness; float a2 = a*a; float NdotH = max(dot(N,H),0.0); float NdotH2 = NdotH*NdotH; float num = a2; float denom = NdotH2*(a2-1.0)+1.0; denom = 3.14159265*denom*denom; return num/denom; }",
3951 dependencies: &[],
3952};
3953
3954pub const SNIPPET_GEOMETRY_SMITH: GlslSnippet = GlslSnippet {
3955 name: "geometry_smith",
3956 code: "float geometry_schlick_ggx(float NdotV, float r) { float r2=(r+1.0); float k=r2*r2/8.0; return NdotV/(NdotV*(1.0-k)+k); } float geometry_smith(vec3 N, vec3 V, vec3 L, float r) { return geometry_schlick_ggx(max(dot(N,V),0.0),r)*geometry_schlick_ggx(max(dot(N,L),0.0),r); }",
3957 dependencies: &[],
3958};
3959
3960pub const SNIPPET_SHADOW_PCF: GlslSnippet = GlslSnippet {
3961 name: "shadow_pcf",
3962 code: "float shadow_pcf(sampler2DShadow shadowMap, vec4 shadowCoord) { float shadow = 0.0; vec2 texelSize = 1.0/textureSize(shadowMap,0); for(int x=-1;x<=1;++x) for(int y=-1;y<=1;++y) shadow += texture(shadowMap, shadowCoord.xyz/shadowCoord.w + vec3(vec2(x,y)*texelSize,0)); return shadow/9.0; }",
3963 dependencies: &[],
3964};
3965
3966pub const SNIPPET_NORMAL_FROM_MAP: GlslSnippet = GlslSnippet {
3967 name: "normal_from_map",
3968 code: "vec3 normal_from_map(sampler2D normalMap, vec2 uv, vec3 worldNormal, vec3 worldTangent) { vec3 n = texture(normalMap, uv).xyz * 2.0 - 1.0; vec3 T = normalize(worldTangent - dot(worldTangent,worldNormal)*worldNormal); vec3 B = cross(worldNormal, T); mat3 TBN = mat3(T,B,worldNormal); return normalize(TBN * n); }",
3969 dependencies: &[],
3970};
3971
3972pub const SNIPPET_PARALLAX_OCCLUSION: GlslSnippet = GlslSnippet {
3973 name: "parallax_occlusion",
3974 code: "vec2 parallax_mapping(sampler2D heightMap, vec2 uv, vec3 viewDir, float scale) { float numLayers = mix(32.0, 8.0, abs(dot(vec3(0,0,1),viewDir))); float layerDepth = 1.0/numLayers; float currentDepth = 0.0; vec2 P = viewDir.xy/viewDir.z*scale; vec2 deltaTexCoords = P/numLayers; vec2 currentTexCoords = uv; float currentDepthMapValue = 1.0 - texture(heightMap,currentTexCoords).r; while(currentDepth < currentDepthMapValue) { currentTexCoords -= deltaTexCoords; currentDepthMapValue = 1.0 - texture(heightMap,currentTexCoords).r; currentDepth += layerDepth; } return currentTexCoords; }",
3975 dependencies: &[],
3976};
3977
3978#[derive(Clone, Debug)]
3981pub struct ShaderLibrary {
3982 pub snippets: HashMap<String, String>,
3983 pub categories: HashMap<String, Vec<String>>,
3984}
3985
3986impl ShaderLibrary {
3987 pub fn new() -> Self { Self { snippets: HashMap::new(), categories: HashMap::new() } }
3988 pub fn add(&mut self, name: impl Into<String>, code: impl Into<String>, category: impl Into<String>) {
3989 let n = name.into(); let cat = category.into();
3990 self.snippets.insert(n.clone(), code.into());
3991 self.categories.entry(cat).or_default().push(n);
3992 }
3993 pub fn get(&self, name: &str) -> Option<&str> { self.snippets.get(name).map(|s| s.as_str()) }
3994 pub fn in_category(&self, cat: &str) -> Vec<&str> {
3995 self.categories.get(cat).map(|names| names.iter().map(|n| self.snippets.get(n.as_str()).map(|_| n.as_str()).unwrap_or("")).collect()).unwrap_or_default()
3996 }
3997 pub fn count(&self) -> usize { self.snippets.len() }
3998 pub fn build_standard() -> Self {
3999 let mut lib = Self::new();
4000 lib.add("gamma_correct", SNIPPET_GAMMA_CORRECT.code, "color");
4001 lib.add("linear_to_srgb", SNIPPET_LINEAR_TO_SRGB.code, "color");
4002 lib.add("srgb_to_linear", SNIPPET_SRGB_TO_LINEAR.code, "color");
4003 lib.add("aces_tonemap", SNIPPET_ACES_TONEMAP.code, "tonemap");
4004 lib.add("reinhard", SNIPPET_REINHARD_TONEMAP.code, "tonemap");
4005 lib.add("fresnel_schlick", SNIPPET_FRESNEL.code, "pbr");
4006 lib.add("ndf_ggx", SNIPPET_NDF_GGX.code, "pbr");
4007 lib.add("geometry_smith", SNIPPET_GEOMETRY_SMITH.code, "pbr");
4008 lib.add("shadow_pcf", SNIPPET_SHADOW_PCF.code, "shadow");
4009 lib.add("normal_from_map", SNIPPET_NORMAL_FROM_MAP.code, "normal");
4010 lib.add("parallax_occlusion", SNIPPET_PARALLAX_OCCLUSION.code, "normal");
4011 lib
4012 }
4013}
4014
4015impl Default for ShaderLibrary {
4016 fn default() -> Self { Self::build_standard() }
4017}
4018
4019#[derive(Clone, Debug)]
4022pub struct ShaderDebugOutput {
4023 pub name: String,
4024 pub output_type: DebugOutputType,
4025 pub channel_mask: [bool; 4],
4026 pub scale: f32,
4027 pub bias: f32,
4028 pub enabled: bool,
4029}
4030
4031#[derive(Clone, Debug, PartialEq)]
4032pub enum DebugOutputType { Albedo, Normal, Roughness, Metalness, Depth, AmbientOcclusion, Emission, Velocity, ShadowMap, LightContribution, Custom(String) }
4033
4034impl ShaderDebugOutput {
4035 pub fn new(name: impl Into<String>, output_type: DebugOutputType) -> Self {
4036 Self { name: name.into(), output_type, channel_mask: [true; 4], scale: 1.0, bias: 0.0, enabled: true }
4037 }
4038 pub fn depth() -> Self { let mut d = Self::new("Depth", DebugOutputType::Depth); d.scale = 100.0; d }
4039 pub fn normal() -> Self { let mut d = Self::new("Normal", DebugOutputType::Normal); d.bias = 0.5; d }
4040 pub fn glsl_code(&self) -> String {
4041 format!("// Debug output: {}", self.name)
4042 }
4043}
4044
4045#[derive(Clone, Debug)]
4046pub struct ShaderDebugger {
4047 pub outputs: Vec<ShaderDebugOutput>,
4048 pub active_output: Option<usize>,
4049 pub enabled: bool,
4050 pub show_overdraw: bool,
4051 pub show_wireframe: bool,
4052 pub show_normals: bool,
4053}
4054
4055impl ShaderDebugger {
4056 pub fn new() -> Self { Self { outputs: Vec::new(), active_output: None, enabled: false, show_overdraw: false, show_wireframe: false, show_normals: false } }
4057 pub fn add_output(&mut self, output: ShaderDebugOutput) { self.outputs.push(output); }
4058 pub fn activate(&mut self, idx: usize) { self.active_output = Some(idx); self.enabled = true; }
4059 pub fn deactivate(&mut self) { self.active_output = None; self.enabled = false; }
4060 pub fn active(&self) -> Option<&ShaderDebugOutput> { self.active_output.and_then(|i| self.outputs.get(i)) }
4061 pub fn output_count(&self) -> usize { self.outputs.len() }
4062 pub fn build_defaults() -> Self {
4063 let mut d = Self::new();
4064 d.add_output(ShaderDebugOutput::new("Albedo", DebugOutputType::Albedo));
4065 d.add_output(ShaderDebugOutput::normal());
4066 d.add_output(ShaderDebugOutput::new("Roughness", DebugOutputType::Roughness));
4067 d.add_output(ShaderDebugOutput::new("Metalness", DebugOutputType::Metalness));
4068 d.add_output(ShaderDebugOutput::depth());
4069 d.add_output(ShaderDebugOutput::new("AO", DebugOutputType::AmbientOcclusion));
4070 d
4071 }
4072}
4073
4074impl Default for ShaderDebugger {
4075 fn default() -> Self { Self::build_defaults() }
4076}
4077
4078#[derive(Clone, Debug, Default)]
4081pub struct ShaderProfileEntry {
4082 pub shader_id: u32,
4083 pub shader_name: String,
4084 pub gpu_time_ms: f32,
4085 pub draw_calls: u32,
4086 pub triangles: u64,
4087 pub frame_count: u64,
4088 pub avg_gpu_ms: f32,
4089}
4090
4091impl ShaderProfileEntry {
4092 pub fn new(shader_id: u32, name: impl Into<String>) -> Self { Self { shader_id, shader_name: name.into(), ..Default::default() } }
4093 pub fn record_frame(&mut self, gpu_ms: f32, draws: u32, tris: u64) {
4094 self.gpu_time_ms = gpu_ms;
4095 self.draw_calls = draws;
4096 self.triangles = tris;
4097 self.avg_gpu_ms = (self.avg_gpu_ms * self.frame_count as f32 + gpu_ms) / (self.frame_count + 1) as f32;
4098 self.frame_count += 1;
4099 }
4100 pub fn is_expensive(&self) -> bool { self.avg_gpu_ms > 2.0 }
4101}
4102
4103#[derive(Clone, Debug)]
4104pub struct ShaderProfiler {
4105 pub entries: HashMap<u32, ShaderProfileEntry>,
4106 pub enabled: bool,
4107 pub frame: u64,
4108}
4109
4110impl ShaderProfiler {
4111 pub fn new() -> Self { Self { entries: HashMap::new(), enabled: false, frame: 0 } }
4112 pub fn enable(&mut self) { self.enabled = true; }
4113 pub fn disable(&mut self) { self.enabled = false; }
4114 pub fn begin_frame(&mut self) { self.frame += 1; }
4115 pub fn record(&mut self, shader_id: u32, name: &str, gpu_ms: f32, draws: u32, tris: u64) {
4116 if !self.enabled { return; }
4117 self.entries.entry(shader_id).or_insert_with(|| ShaderProfileEntry::new(shader_id, name)).record_frame(gpu_ms, draws, tris);
4118 }
4119 pub fn expensive_shaders(&self) -> Vec<&ShaderProfileEntry> { self.entries.values().filter(|e| e.is_expensive()).collect() }
4120 pub fn total_gpu_ms(&self) -> f32 { self.entries.values().map(|e| e.gpu_time_ms).sum() }
4121 pub fn shader_count(&self) -> usize { self.entries.len() }
4122}
4123
4124impl Default for ShaderProfiler {
4125 fn default() -> Self { Self::new() }
4126}
4127
4128#[derive(Clone, Debug)]
4131pub struct HotReloadManager {
4132 pub watcher: ShaderFileWatcher,
4133 pub registry: ShaderProgramRegistry,
4134 pub reload_count: u32,
4135 pub last_reload_time: f32,
4136 pub reload_on_focus: bool,
4137 pub auto_save_on_compile: bool,
4138}
4139
4140impl HotReloadManager {
4141 pub fn new() -> Self { Self { watcher: ShaderFileWatcher::new(), registry: ShaderProgramRegistry::new(), reload_count: 0, last_reload_time: 0.0, reload_on_focus: true, auto_save_on_compile: false } }
4142 pub fn watch_shader(&mut self, path: impl Into<String>, shader_id: u32) { self.watcher.watch(path, shader_id); }
4143 pub fn tick(&mut self, _dt: f32) -> Vec<u32> {
4144 let mut reloaded = Vec::new();
4145 while let Some(path) = self.watcher.next_reload() {
4146 let ids = self.watcher.shader_ids_for(&path);
4147 reloaded.extend_from_slice(&ids);
4148 self.reload_count += ids.len() as u32;
4149 }
4150 reloaded
4151 }
4152 pub fn force_reload_all(&mut self) {
4153 let paths: Vec<_> = self.watcher.watched_files.keys().cloned().collect();
4154 for path in paths { self.watcher.mark_modified(&path); }
4155 }
4156 pub fn reload_count(&self) -> u32 { self.reload_count }
4157}
4158
4159impl Default for HotReloadManager {
4160 fn default() -> Self { Self::new() }
4161}
4162
4163pub const LIGHTING_MODEL_COUNT: usize = 7;
4166pub const SHADER_LIBRARY_STANDARD_SNIPPET_COUNT: usize = 11;
4167pub const SHADER_DEBUG_OUTPUT_MAX: usize = 16;
4168pub const SHADER_PROFILER_EXPENSIVE_THRESHOLD_MS: f32 = 2.0;
4169pub const SHADER_HOT_RELOAD_DEBOUNCE_MS: u32 = 300;
4170pub const MATERIAL_MAX_BLEND_MODES: usize = 8;
4171pub const SHADER_SNIPPET_MAX_SIZE_CHARS: usize = 4096;
4172pub const GLSL_MAX_VARYING_COMPONENTS: u32 = 128;
4173pub const GLSL_MAX_TEXTURE_SAMPLERS: u32 = 16;
4174pub const GLSL_MAX_UNIFORM_BLOCKS: u32 = 14;
4175pub const WGSL_MAX_BIND_GROUPS: u32 = 4;
4176pub const WGSL_MAX_BINDINGS_PER_GROUP: u32 = 8;
4177
4178pub fn lighting_model_name(model: &LightingModel) -> &str { &model.name }
4179pub fn shader_system_info() -> String { format!("ShaderSystem: compiler + materials + textures + buffers + post-process + lighting + hot-reload") }
4180
4181
4182#[derive(Clone, Debug)]
4185pub struct ShaderPermutationKey {
4186 pub features: Vec<String>,
4187}
4188
4189impl ShaderPermutationKey {
4190 pub fn new() -> Self { Self { features: Vec::new() } }
4191 pub fn with(mut self, feature: impl Into<String>) -> Self { self.features.push(feature.into()); self }
4192 pub fn add(&mut self, feature: impl Into<String>) { self.features.push(feature.into()); }
4193 pub fn hash_key(&self) -> u64 {
4194 let mut sorted = self.features.clone(); sorted.sort();
4195 sorted.iter().fold(14695981039346656037u64, |h, s| s.bytes().fold(h, |h, b| h.wrapping_mul(1099511628211) ^ b as u64))
4196 }
4197 pub fn has(&self, feature: &str) -> bool { self.features.contains(&feature.to_string()) }
4198 pub fn feature_count(&self) -> usize { self.features.len() }
4199 pub fn is_empty(&self) -> bool { self.features.is_empty() }
4200}
4201
4202impl Default for ShaderPermutationKey {
4203 fn default() -> Self { Self::new() }
4204}
4205
4206#[derive(Clone, Debug)]
4207pub struct ShaderPermutationRegistry {
4208 pub permutations: HashMap<u64, ShaderProgram>,
4209 pub keys: HashMap<u64, ShaderPermutationKey>,
4210 pub max_permutations: usize,
4211 pub base_program_id: u32,
4212}
4213
4214impl ShaderPermutationRegistry {
4215 pub fn new(base_id: u32, max: usize) -> Self { Self { permutations: HashMap::new(), keys: HashMap::new(), max_permutations: max, base_program_id: base_id } }
4216 pub fn register(&mut self, key: ShaderPermutationKey, program: ShaderProgram) -> bool {
4217 if self.permutations.len() >= self.max_permutations { return false; }
4218 let h = key.hash_key();
4219 self.permutations.insert(h, program);
4220 self.keys.insert(h, key);
4221 true
4222 }
4223 pub fn find(&self, key: &ShaderPermutationKey) -> Option<&ShaderProgram> { self.permutations.get(&key.hash_key()) }
4224 pub fn count(&self) -> usize { self.permutations.len() }
4225 pub fn clear(&mut self) { self.permutations.clear(); self.keys.clear(); }
4226}
4227
4228#[derive(Clone, Debug)]
4231pub struct ShaderBindingLayout {
4232 pub set: u32,
4233 pub bindings: Vec<DescriptorBinding>,
4234}
4235
4236#[derive(Clone, Debug)]
4237pub struct DescriptorBinding {
4238 pub binding: u32,
4239 pub descriptor_type: DescriptorType,
4240 pub count: u32,
4241 pub stages: Vec<ShaderStage>,
4242 pub name: String,
4243}
4244
4245#[derive(Clone, Debug, PartialEq)]
4246pub enum DescriptorType { UniformBuffer, StorageBuffer, Sampler, SampledImage, StorageImage, CombinedImageSampler, InputAttachment }
4247
4248#[derive(Clone, Debug, PartialEq)]
4249pub enum ShaderStage { Vertex, Fragment, Geometry, TessControl, TessEval, Compute, All }
4250
4251impl ShaderBindingLayout {
4252 pub fn new(set: u32) -> Self { Self { set, bindings: Vec::new() } }
4253 pub fn add_binding(&mut self, binding: u32, desc_type: DescriptorType, name: impl Into<String>) {
4254 self.bindings.push(DescriptorBinding { binding, descriptor_type: desc_type, count: 1, stages: vec![ShaderStage::All], name: name.into() });
4255 }
4256 pub fn binding_count(&self) -> usize { self.bindings.len() }
4257 pub fn has_binding(&self, binding: u32) -> bool { self.bindings.iter().any(|b| b.binding == binding) }
4258}
4259
4260#[derive(Clone, Debug)]
4261pub struct PipelineLayout {
4262 pub descriptor_sets: Vec<ShaderBindingLayout>,
4263 pub push_constant_size: u32,
4264 pub push_constant_stages: Vec<ShaderStage>,
4265}
4266
4267impl PipelineLayout {
4268 pub fn new() -> Self { Self { descriptor_sets: Vec::new(), push_constant_size: 0, push_constant_stages: Vec::new() } }
4269 pub fn add_set(&mut self, layout: ShaderBindingLayout) { self.descriptor_sets.push(layout); }
4270 pub fn set_push_constants(&mut self, size: u32) { self.push_constant_size = size; self.push_constant_stages = vec![ShaderStage::Vertex, ShaderStage::Fragment]; }
4271 pub fn set_count(&self) -> usize { self.descriptor_sets.len() }
4272 pub fn has_push_constants(&self) -> bool { self.push_constant_size > 0 }
4273 pub fn total_bindings(&self) -> usize { self.descriptor_sets.iter().map(|s| s.binding_count()).sum() }
4274}
4275
4276impl Default for PipelineLayout {
4277 fn default() -> Self { Self::new() }
4278}
4279
4280#[derive(Clone, Debug)]
4283pub struct CompilePipelineConfig {
4284 pub optimizer: ShaderOptimizer,
4285 pub codegen: ShaderCodeGen,
4286 pub preprocessor: ShaderPreprocessor,
4287 pub include_resolver: ShaderIncludeResolver,
4288 pub cache: ShaderCache,
4289 pub diagnostic_limit: usize,
4290 pub strict_mode: bool,
4291 pub pedantic: bool,
4292}
4293
4294impl CompilePipelineConfig {
4295 pub fn new(target: CodeGenTarget) -> Self {
4296 Self { optimizer: ShaderOptimizer::default(), codegen: ShaderCodeGen::new(target), preprocessor: ShaderPreprocessor::new(), include_resolver: ShaderIncludeResolver::new(), cache: ShaderCache::default(), diagnostic_limit: SHADER_DIAGNOSTIC_MAX, strict_mode: false, pedantic: false }
4297 }
4298 pub fn debug_config(target: CodeGenTarget) -> Self {
4299 let mut c = Self::new(target); c.optimizer = ShaderOptimizer::debug(); c
4300 }
4301 pub fn release_config(target: CodeGenTarget) -> Self {
4302 let mut c = Self::new(target); c.optimizer = ShaderOptimizer::release(); c
4303 }
4304 pub fn add_define(&mut self, key: impl Into<String>, val: impl Into<String>) { self.preprocessor.define(key, val); }
4305}
4306
4307#[derive(Clone, Debug)]
4308pub struct CompileResult {
4309 pub success: bool,
4310 pub spirv_bytes: Option<Vec<u8>>,
4311 pub diagnostics: DiagnosticList,
4312 pub reflection: ShaderReflection,
4313 pub compile_time_ms: f32,
4314 pub source_lines: u32,
4315 pub optimized: bool,
4316}
4317
4318impl CompileResult {
4319 pub fn ok(spirv: Vec<u8>, reflection: ShaderReflection) -> Self {
4320 Self { success: true, spirv_bytes: Some(spirv), diagnostics: DiagnosticList::new(), reflection, compile_time_ms: 0.0, source_lines: 0, optimized: false }
4321 }
4322 pub fn fail(diagnostics: DiagnosticList) -> Self {
4323 Self { success: false, spirv_bytes: None, diagnostics, reflection: ShaderReflection::new(), compile_time_ms: 0.0, source_lines: 0, optimized: false }
4324 }
4325 pub fn error_count(&self) -> usize { self.diagnostics.error_count() }
4326 pub fn warning_count(&self) -> usize { self.diagnostics.warning_count() }
4327 pub fn spirv_size(&self) -> usize { self.spirv_bytes.as_ref().map(|b| b.len()).unwrap_or(0) }
4328 pub fn has_warnings(&self) -> bool { self.diagnostics.warning_count() > 0 }
4329}
4330
4331#[derive(Clone, Debug)]
4334pub struct ShaderEditorState {
4335 pub active_program_id: Option<u32>,
4336 pub active_stage: ShaderStage,
4337 pub cursor_line: u32,
4338 pub cursor_col: u32,
4339 pub scroll_top: u32,
4340 pub selection: Option<(u32, u32, u32, u32)>,
4341 pub font_size: f32,
4342 pub show_line_numbers: bool,
4343 pub show_minimap: bool,
4344 pub syntax_highlight: bool,
4345 pub auto_complete: bool,
4346 pub bracket_matching: bool,
4347 pub indent_guides: bool,
4348 pub word_wrap: bool,
4349 pub theme: String,
4350 pub undo_stack: VecDeque<String>,
4351 pub redo_stack: Vec<String>,
4352 pub modified: bool,
4353}
4354
4355impl ShaderEditorState {
4356 pub fn new() -> Self {
4357 Self { active_program_id: None, active_stage: ShaderStage::Fragment, cursor_line: 0, cursor_col: 0, scroll_top: 0, selection: None, font_size: 14.0, show_line_numbers: true, show_minimap: true, syntax_highlight: true, auto_complete: true, bracket_matching: true, indent_guides: true, word_wrap: false, theme: "dark".into(), undo_stack: VecDeque::new(), redo_stack: Vec::new(), modified: false }
4358 }
4359 pub fn set_active(&mut self, id: u32, stage: ShaderStage) { self.active_program_id = Some(id); self.active_stage = stage; self.cursor_line = 0; self.cursor_col = 0; }
4360 pub fn move_cursor(&mut self, line: u32, col: u32) { self.cursor_line = line; self.cursor_col = col; }
4361 pub fn push_undo(&mut self, desc: impl Into<String>) { if self.undo_stack.len() >= 100 { self.undo_stack.pop_front(); } self.undo_stack.push_back(desc.into()); self.redo_stack.clear(); self.modified = true; }
4362 pub fn can_undo(&self) -> bool { !self.undo_stack.is_empty() }
4363 pub fn can_redo(&self) -> bool { !self.redo_stack.is_empty() }
4364 pub fn undo(&mut self) -> Option<String> { let v = self.undo_stack.pop_back()?; self.redo_stack.push(v.clone()); Some(v) }
4365 pub fn redo(&mut self) -> Option<String> { let v = self.redo_stack.pop()?; self.undo_stack.push_back(v.clone()); Some(v) }
4366 pub fn mark_saved(&mut self) { self.modified = false; }
4367 pub fn increase_font(&mut self) { self.font_size = (self.font_size + 1.0).min(48.0); }
4368 pub fn decrease_font(&mut self) { self.font_size = (self.font_size - 1.0).max(8.0); }
4369}
4370
4371impl Default for ShaderEditorState {
4372 fn default() -> Self { Self::new() }
4373}
4374
4375#[derive(Clone, Debug)]
4378pub struct AutoCompleteItem {
4379 pub label: String,
4380 pub kind: AutoCompleteKind,
4381 pub detail: String,
4382 pub insert_text: String,
4383 pub documentation: String,
4384}
4385
4386#[derive(Clone, Debug, PartialEq)]
4387pub enum AutoCompleteKind { Function, Variable, Type, Keyword, Snippet, Uniform }
4388
4389impl AutoCompleteItem {
4390 pub fn function(name: impl Into<String>, signature: impl Into<String>) -> Self {
4391 let n = name.into();
4392 Self { label: n.clone(), kind: AutoCompleteKind::Function, detail: signature.into(), insert_text: n, documentation: String::new() }
4393 }
4394 pub fn type_item(name: &'static str) -> Self {
4395 Self { label: name.into(), kind: AutoCompleteKind::Type, detail: "GLSL type".into(), insert_text: name.into(), documentation: String::new() }
4396 }
4397 pub fn keyword(kw: &'static str) -> Self {
4398 Self { label: kw.into(), kind: AutoCompleteKind::Keyword, detail: "keyword".into(), insert_text: kw.into(), documentation: String::new() }
4399 }
4400}
4401
4402#[derive(Clone, Debug)]
4403pub struct AutoCompleteProvider {
4404 pub builtins: Vec<AutoCompleteItem>,
4405 pub user_symbols: Vec<AutoCompleteItem>,
4406 pub max_results: usize,
4407}
4408
4409impl AutoCompleteProvider {
4410 pub fn new() -> Self {
4411 let mut builtins = Vec::new();
4412 for kw in &["void", "float", "int", "uint", "bool", "vec2", "vec3", "vec4", "mat3", "mat4", "sampler2D"] {
4413 builtins.push(AutoCompleteItem::type_item(kw));
4414 }
4415 for kw in &["if", "else", "for", "while", "return", "break", "continue", "discard", "uniform", "in", "out", "const"] {
4416 builtins.push(AutoCompleteItem::keyword(kw));
4417 }
4418 for (name, sig) in &[("normalize", "vec3 normalize(vec3)"), ("length", "float length(genType)"), ("dot", "float dot(genType, genType)"), ("cross", "vec3 cross(vec3, vec3)"), ("reflect", "genType reflect(genType, genType)"), ("refract", "genType refract(genType, genType, float)"), ("mix", "genType mix(genType, genType, genType)"), ("clamp", "genType clamp(genType, genType, genType)"), ("pow", "genType pow(genType, genType)"), ("sqrt", "genType sqrt(genType)"), ("abs", "genType abs(genType)"), ("max", "genType max(genType, genType)"), ("min", "genType min(genType, genType)"), ("texture", "vec4 texture(sampler2D, vec2)"), ("step", "genType step(genType, genType)"), ("smoothstep", "genType smoothstep(genType, genType, genType)"), ("sin", "genType sin(genType)"), ("cos", "genType cos(genType)"), ("tan", "genType tan(genType)"), ("floor", "genType floor(genType)"), ("ceil", "genType ceil(genType)"), ("fract", "genType fract(genType)"), ("mod", "genType mod(genType, genType)"), ("sign", "genType sign(genType)"), ("distance", "float distance(genType, genType)"), ("transpose", "mat transpose(mat)"), ("inverse", "mat inverse(mat)"), ("determinant", "float determinant(mat)")] {
4419 builtins.push(AutoCompleteItem::function(*name, *sig));
4420 }
4421 Self { builtins, user_symbols: Vec::new(), max_results: 20 }
4422 }
4423 pub fn query(&self, prefix: &str) -> Vec<&AutoCompleteItem> {
4424 let lower = prefix.to_lowercase();
4425 let mut results: Vec<_> = self.builtins.iter().chain(self.user_symbols.iter()).filter(|item| item.label.to_lowercase().starts_with(&lower)).collect();
4426 results.truncate(self.max_results);
4427 results
4428 }
4429 pub fn add_user_symbol(&mut self, item: AutoCompleteItem) { self.user_symbols.push(item); }
4430 pub fn clear_user_symbols(&mut self) { self.user_symbols.clear(); }
4431 pub fn builtin_count(&self) -> usize { self.builtins.len() }
4432}
4433
4434impl Default for AutoCompleteProvider {
4435 fn default() -> Self { Self::new() }
4436}
4437
4438pub const SHADER_EDITOR_MAX_UNDO: usize = 200;
4441pub const SHADER_AUTOCOMPLETE_MAX_RESULTS: usize = 20;
4442pub const SHADER_PERMUTATION_MAX: usize = 512;
4443pub const SHADER_BINDING_SET_MAX: usize = 4;
4444pub const SHADER_BINDING_MAX_PER_SET: usize = 16;
4445pub const SHADER_STAGE_COUNT: usize = 6;
4446pub const SHADER_PUSH_CONSTANT_MAX_BYTES: u32 = 128;
4447
4448pub fn shader_stage_name(stage: &ShaderStage) -> &'static str {
4449 match stage { ShaderStage::Vertex => "vertex", ShaderStage::Fragment => "fragment", ShaderStage::Geometry => "geometry", ShaderStage::TessControl => "tess_control", ShaderStage::TessEval => "tess_eval", ShaderStage::Compute => "compute", ShaderStage::All => "all" }
4450}
4451pub fn descriptor_type_name(dt: &DescriptorType) -> &'static str {
4452 match dt { DescriptorType::UniformBuffer => "UniformBuffer", DescriptorType::StorageBuffer => "StorageBuffer", DescriptorType::Sampler => "Sampler", DescriptorType::SampledImage => "SampledImage", DescriptorType::StorageImage => "StorageImage", DescriptorType::CombinedImageSampler => "CombinedImageSampler", DescriptorType::InputAttachment => "InputAttachment" }
4453}
4454
4455
4456#[derive(Clone, Debug)]
4459pub struct ShaderInterface {
4460 pub inputs: Vec<ShaderVariable>,
4461 pub outputs: Vec<ShaderVariable>,
4462 pub stage: ShaderStage,
4463}
4464
4465impl ShaderInterface {
4466 pub fn new(stage: ShaderStage) -> Self { Self { inputs: Vec::new(), outputs: Vec::new(), stage } }
4467 pub fn add_input(&mut self, var: ShaderVariable) { self.inputs.push(var); }
4468 pub fn add_output(&mut self, var: ShaderVariable) { self.outputs.push(var); }
4469 pub fn input_count(&self) -> usize { self.inputs.len() }
4470 pub fn output_count(&self) -> usize { self.outputs.len() }
4471 pub fn find_input(&self, name: &str) -> Option<&ShaderVariable> { self.inputs.iter().find(|v| v.name == name) }
4472 pub fn find_output(&self, name: &str) -> Option<&ShaderVariable> { self.outputs.iter().find(|v| v.name == name) }
4473 pub fn validate_compatible(&self, next: &ShaderInterface) -> Vec<String> {
4474 let mut errors = Vec::new();
4475 for out in &self.outputs {
4476 if !next.inputs.iter().any(|i| i.name == out.name && i.data_type == out.data_type) {
4477 errors.push(format!("Output '{}' not matched in next stage", out.name));
4478 }
4479 }
4480 errors
4481 }
4482 pub fn total_output_components(&self) -> u32 { self.outputs.iter().map(|o| o.data_type.byte_size() / 4).sum() }
4483}
4484
4485#[derive(Clone, Debug)]
4488pub struct ShaderLinker {
4489 pub programs: HashMap<u32, ShaderProgram>,
4490 pub layouts: HashMap<u32, PipelineLayout>,
4491 pub link_errors: Vec<String>,
4492}
4493
4494impl ShaderLinker {
4495 pub fn new() -> Self { Self { programs: HashMap::new(), layouts: HashMap::new(), link_errors: Vec::new() } }
4496 pub fn add_program(&mut self, prog: ShaderProgram) { self.programs.insert(prog.id, prog); }
4497 pub fn link(&mut self, prog_id: u32) -> bool {
4498 self.link_errors.clear();
4499 if !self.programs.contains_key(&prog_id) { self.link_errors.push("Program not found".into()); return false; }
4500 true
4501 }
4502 pub fn has_errors(&self) -> bool { !self.link_errors.is_empty() }
4503 pub fn error_count(&self) -> usize { self.link_errors.len() }
4504 pub fn clear_errors(&mut self) { self.link_errors.clear(); }
4505}
4506
4507impl Default for ShaderLinker {
4508 fn default() -> Self { Self::new() }
4509}
4510
4511#[derive(Clone, Debug)]
4514pub struct ShaderExportOptions {
4515 pub target: CodeGenTarget,
4516 pub minify: bool,
4517 pub include_reflection: bool,
4518 pub include_source: bool,
4519 pub embed_spirv: bool,
4520 pub output_format: ShaderExportFormat,
4521 pub output_path: String,
4522}
4523
4524#[derive(Clone, Debug, PartialEq)]
4525pub enum ShaderExportFormat { Glsl, SpirV, Wgsl, Json, Binary }
4526
4527impl ShaderExportOptions {
4528 pub fn new(target: CodeGenTarget) -> Self {
4529 Self { target, minify: false, include_reflection: true, include_source: true, embed_spirv: false, output_format: ShaderExportFormat::Glsl, output_path: "./shaders".into() }
4530 }
4531 pub fn release(target: CodeGenTarget) -> Self {
4532 Self { minify: true, include_source: false, ..Self::new(target) }
4533 }
4534 pub fn spirv(target: CodeGenTarget) -> Self {
4535 Self { embed_spirv: true, output_format: ShaderExportFormat::SpirV, ..Self::new(target) }
4536 }
4537}
4538
4539#[derive(Clone, Debug)]
4540pub struct ShaderExportResult {
4541 pub success: bool,
4542 pub programs_exported: u32,
4543 pub files_written: Vec<String>,
4544 pub errors: Vec<String>,
4545 pub warnings: Vec<String>,
4546 pub total_bytes: u64,
4547 pub duration_ms: f32,
4548}
4549
4550impl ShaderExportResult {
4551 pub fn ok(count: u32, files: Vec<String>, bytes: u64) -> Self {
4552 Self { success: true, programs_exported: count, files_written: files, errors: Vec::new(), warnings: Vec::new(), total_bytes: bytes, duration_ms: 0.0 }
4553 }
4554 pub fn fail(msg: impl Into<String>) -> Self {
4555 Self { success: false, programs_exported: 0, files_written: Vec::new(), errors: vec![msg.into()], warnings: Vec::new(), total_bytes: 0, duration_ms: 0.0 }
4556 }
4557}
4558
4559#[derive(Clone, Debug)]
4562pub struct LintRule {
4563 pub id: u32,
4564 pub name: String,
4565 pub description: String,
4566 pub severity: DiagnosticKind,
4567 pub enabled: bool,
4568 pub pattern: String,
4569}
4570
4571impl LintRule {
4572 pub fn new(id: u32, name: impl Into<String>, severity: DiagnosticKind) -> Self {
4573 Self { id, name: name.into(), description: String::new(), severity, enabled: true, pattern: String::new() }
4574 }
4575 pub fn error(id: u32, name: impl Into<String>) -> Self { Self::new(id, name, DiagnosticKind::Error) }
4576 pub fn warn(id: u32, name: impl Into<String>) -> Self { Self::new(id, name, DiagnosticKind::Warning) }
4577 pub fn with_desc(mut self, desc: impl Into<String>) -> Self { self.description = desc.into(); self }
4578}
4579
4580#[derive(Clone, Debug)]
4581pub struct ShaderLinter {
4582 pub rules: Vec<LintRule>,
4583 pub enabled: bool,
4584}
4585
4586impl ShaderLinter {
4587 pub fn new() -> Self {
4588 let mut rules = Vec::new();
4589 rules.push(LintRule::warn(1, "unused_variable").with_desc("Variable declared but never used"));
4590 rules.push(LintRule::warn(2, "precision_missing").with_desc("Missing precision qualifier"));
4591 rules.push(LintRule::error(3, "undefined_variable").with_desc("Variable used before declaration"));
4592 rules.push(LintRule::warn(4, "divide_by_zero").with_desc("Potential division by constant zero"));
4593 rules.push(LintRule::warn(5, "comparison_float").with_desc("Float equality comparison may be imprecise"));
4594 rules.push(LintRule::warn(6, "redundant_cast").with_desc("Unnecessary type cast"));
4595 rules.push(LintRule::error(7, "wrong_argument_type").with_desc("Function argument type mismatch"));
4596 rules.push(LintRule::warn(8, "shadowed_variable").with_desc("Variable shadows outer scope declaration"));
4597 Self { rules, enabled: true }
4598 }
4599 pub fn lint(&self, _source: &str) -> DiagnosticList { DiagnosticList::new() }
4600 pub fn enabled_rules(&self) -> Vec<&LintRule> { self.rules.iter().filter(|r| r.enabled).collect() }
4601 pub fn disable_rule(&mut self, id: u32) { if let Some(r) = self.rules.iter_mut().find(|r| r.id == id) { r.enabled = false; } }
4602 pub fn enable_rule(&mut self, id: u32) { if let Some(r) = self.rules.iter_mut().find(|r| r.id == id) { r.enabled = true; } }
4603 pub fn rule_count(&self) -> usize { self.rules.len() }
4604}
4605
4606impl Default for ShaderLinter {
4607 fn default() -> Self { Self::new() }
4608}
4609
4610#[derive(Clone, Debug)]
4613pub struct ShaderFormatterConfig {
4614 pub indent_size: usize,
4615 pub use_tabs: bool,
4616 pub max_line_length: usize,
4617 pub brace_style: BraceStyle,
4618 pub space_around_operators: bool,
4619 pub space_after_comma: bool,
4620 pub trailing_newline: bool,
4621}
4622
4623#[derive(Clone, Debug, PartialEq)]
4624pub enum BraceStyle { Allman, KnR, Stroustrup }
4625
4626impl ShaderFormatterConfig {
4627 pub fn default_style() -> Self {
4628 Self { indent_size: 4, use_tabs: false, max_line_length: 120, brace_style: BraceStyle::KnR, space_around_operators: true, space_after_comma: true, trailing_newline: true }
4629 }
4630 pub fn compact() -> Self {
4631 Self { indent_size: 2, ..Self::default_style() }
4632 }
4633}
4634
4635impl Default for ShaderFormatterConfig {
4636 fn default() -> Self { Self::default_style() }
4637}
4638
4639#[derive(Clone, Debug)]
4640pub struct ShaderFormatter {
4641 pub config: ShaderFormatterConfig,
4642}
4643
4644impl ShaderFormatter {
4645 pub fn new() -> Self { Self { config: ShaderFormatterConfig::default() } }
4646 pub fn format(&self, source: &str) -> String {
4647 let mut output = String::with_capacity(source.len());
4648 let mut indent = 0usize;
4649 for line in source.lines() {
4650 let trimmed = line.trim();
4651 if trimmed.starts_with('}') && indent > 0 { indent -= 1; }
4652 let indent_str = if self.config.use_tabs { "\t".repeat(indent) } else { " ".repeat(indent * self.config.indent_size) };
4653 output.push_str(&indent_str);
4654 output.push_str(trimmed);
4655 output.push('\n');
4656 if trimmed.ends_with('{') { indent += 1; }
4657 }
4658 if self.config.trailing_newline && !output.ends_with('\n') { output.push('\n'); }
4659 output
4660 }
4661 pub fn count_lines(source: &str) -> usize { source.lines().count() }
4662 pub fn count_characters(source: &str) -> usize { source.chars().count() }
4663}
4664
4665impl Default for ShaderFormatter {
4666 fn default() -> Self { Self::new() }
4667}
4668
4669pub const SHADER_LINTER_RULE_COUNT_DEFAULT: usize = 8;
4672pub const SHADER_FORMATTER_MAX_LINE_LENGTH: usize = 120;
4673pub const SHADER_EXPORT_FORMAT_COUNT: usize = 5;
4674pub const SHADER_LINKER_MAX_PROGRAMS: usize = 1024;
4675pub const SHADER_INTERFACE_MAX_VARYINGS: usize = 32;
4676pub const SHADER_PERMUTATION_KEY_MAX_FEATURES: usize = 16;
4677
4678pub fn shader_compiler_module_list() -> &'static [&'static str] {
4679 &[
4680 "tokenizer", "preprocessor", "parser", "ast", "codegen",
4681 "optimizer", "cache", "templates", "variants", "permutations",
4682 "include_resolver", "reflection", "diagnostics", "pipeline_state",
4683 "materials", "textures", "buffers", "render_passes", "post_process",
4684 "node_graph", "hot_reload", "lighting", "library", "debugger",
4685 "profiler", "editor_state", "autocomplete", "linker", "export",
4686 "linter", "formatter", "binding_layout",
4687 ]
4688}
4689
4690pub fn shader_module_count() -> usize { shader_compiler_module_list().len() }
4691
4692pub fn full_shader_system_description() -> String {
4693 format!("ShaderCompiler: {} modules — full pipeline from GLSL source to compiled GPU program", shader_module_count())
4694}
4695
4696
4697#[derive(Clone, Debug)]
4700pub struct UniformTracker {
4701 pub float_uniforms: HashMap<String, f32>,
4702 pub vec2_uniforms: HashMap<String, [f32; 2]>,
4703 pub vec3_uniforms: HashMap<String, [f32; 3]>,
4704 pub vec4_uniforms: HashMap<String, [f32; 4]>,
4705 pub int_uniforms: HashMap<String, i32>,
4706 pub bool_uniforms: HashMap<String, bool>,
4707 pub mat4_uniforms: HashMap<String, [[f32; 4]; 4]>,
4708 pub dirty_flags: HashSet<String>,
4709 pub upload_count: u64,
4710}
4711
4712impl UniformTracker {
4713 pub fn new() -> Self { Self { float_uniforms: HashMap::new(), vec2_uniforms: HashMap::new(), vec3_uniforms: HashMap::new(), vec4_uniforms: HashMap::new(), int_uniforms: HashMap::new(), bool_uniforms: HashMap::new(), mat4_uniforms: HashMap::new(), dirty_flags: HashSet::new(), upload_count: 0 } }
4714 pub fn set_float(&mut self, name: impl Into<String>, v: f32) { let n = name.into(); if self.float_uniforms.get(&n) != Some(&v) { self.float_uniforms.insert(n.clone(), v); self.dirty_flags.insert(n); } }
4715 pub fn set_vec3(&mut self, name: impl Into<String>, v: [f32; 3]) { let n = name.into(); self.vec3_uniforms.insert(n.clone(), v); self.dirty_flags.insert(n); }
4716 pub fn set_vec4(&mut self, name: impl Into<String>, v: [f32; 4]) { let n = name.into(); self.vec4_uniforms.insert(n.clone(), v); self.dirty_flags.insert(n); }
4717 pub fn set_int(&mut self, name: impl Into<String>, v: i32) { let n = name.into(); self.int_uniforms.insert(n.clone(), v); self.dirty_flags.insert(n); }
4718 pub fn set_bool(&mut self, name: impl Into<String>, v: bool) { let n = name.into(); self.bool_uniforms.insert(n.clone(), v); self.dirty_flags.insert(n); }
4719 pub fn mark_uploaded(&mut self) { self.dirty_flags.clear(); self.upload_count += 1; }
4720 pub fn dirty_count(&self) -> usize { self.dirty_flags.len() }
4721 pub fn has_dirty(&self) -> bool { !self.dirty_flags.is_empty() }
4722 pub fn get_float(&self, name: &str) -> Option<f32> { self.float_uniforms.get(name).copied() }
4723 pub fn get_vec4(&self, name: &str) -> Option<[f32; 4]> { self.vec4_uniforms.get(name).copied() }
4724 pub fn total_uniform_count(&self) -> usize { self.float_uniforms.len() + self.vec2_uniforms.len() + self.vec3_uniforms.len() + self.vec4_uniforms.len() + self.int_uniforms.len() + self.bool_uniforms.len() + self.mat4_uniforms.len() }
4725}
4726
4727impl Default for UniformTracker {
4728 fn default() -> Self { Self::new() }
4729}
4730
4731#[derive(Clone, Debug)]
4734pub struct ShaderErrorReport {
4735 pub program_id: u32,
4736 pub program_name: String,
4737 pub stage: String,
4738 pub errors: Vec<ShaderDiagnostic>,
4739 pub warnings: Vec<ShaderDiagnostic>,
4740 pub source_snippet: Vec<(u32, String)>,
4741 pub timestamp: u64,
4742 pub is_fatal: bool,
4743}
4744
4745impl ShaderErrorReport {
4746 pub fn new(program_id: u32, name: impl Into<String>, stage: impl Into<String>) -> Self {
4747 Self { program_id, program_name: name.into(), stage: stage.into(), errors: Vec::new(), warnings: Vec::new(), source_snippet: Vec::new(), timestamp: 0, is_fatal: false }
4748 }
4749 pub fn add_error(&mut self, err: ShaderDiagnostic) { if err.is_error() { self.is_fatal = true; } self.errors.push(err); }
4750 pub fn add_warning(&mut self, w: ShaderDiagnostic) { self.warnings.push(w); }
4751 pub fn add_source_line(&mut self, line_no: u32, line: impl Into<String>) { self.source_snippet.push((line_no, line.into())); }
4752 pub fn has_errors(&self) -> bool { !self.errors.is_empty() }
4753 pub fn format_report(&self) -> String {
4754 let mut s = format!("[{}] {}:{}\n", if self.is_fatal { "FATAL" } else { "WARN" }, self.program_name, self.stage);
4755 for e in &self.errors { s += &format!(" {}\n", e.format()); }
4756 for w in &self.warnings { s += &format!(" {}\n", w.format()); }
4757 s
4758 }
4759}
4760
4761pub fn glsl_preamble(version: &str, is_es: bool) -> String {
4764 if is_es { format!("#version {} es\nprecision highp float;\nprecision highp int;\n", version) }
4765 else { format!("#version {}\n", version) }
4766}
4767
4768pub fn glsl_common_utils() -> &'static str {
4769 r#"
4770const float PI = 3.14159265358979323846;
4771const float TWO_PI = 6.28318530717958647692;
4772const float HALF_PI = 1.57079632679489661923;
4773const float INV_PI = 0.31830988618379067154;
4774const float E = 2.71828182845904523536;
4775const float GOLDEN_RATIO = 1.61803398874989484820;
4776const float EPSILON = 1e-6;
4777const float INF = 1.0/0.0;
4778
4779float saturate(float v) { return clamp(v, 0.0, 1.0); }
4780vec2 saturate(vec2 v) { return clamp(v, vec2(0.0), vec2(1.0)); }
4781vec3 saturate(vec3 v) { return clamp(v, vec3(0.0), vec3(1.0)); }
4782vec4 saturate(vec4 v) { return clamp(v, vec4(0.0), vec4(1.0)); }
4783
4784float remap(float v, float fromMin, float fromMax, float toMin, float toMax) {
4785 return toMin + (v - fromMin) / (fromMax - fromMin) * (toMax - toMin);
4786}
4787float luminance(vec3 c) { return dot(c, vec3(0.2126, 0.7152, 0.0722)); }
4788vec3 rgb_to_hsv(vec3 c) {
4789 vec4 K = vec4(0.0, -1.0/3.0, 2.0/3.0, -1.0);
4790 vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
4791 vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
4792 float d = q.x - min(q.w, q.y);
4793 float e = 1.0e-10;
4794 return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
4795}
4796float rand(vec2 co) { return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453); }
4797vec3 hash33(vec3 p) {
4798 p = fract(p * vec3(443.8975, 397.2973, 491.1871));
4799 p += dot(p.zxy, p.yxz + 19.19);
4800 return fract(vec3(p.x * p.y, p.z * p.x, p.y * p.z));
4801}
4802"#
4803}
4804
4805pub fn glsl_pbr_brdf() -> &'static str {
4806 r#"
4807// PBR BRDF functions (GGX/Schlick/Smith)
4808vec3 F_Schlick(vec3 f0, float f90, float u) {
4809 return f0 + (f90 - f0) * pow(1.0 - u, 5.0);
4810}
4811float V_SmithGGXCorrelated(float NdotV, float NdotL, float roughness) {
4812 float a2 = roughness * roughness;
4813 float GGXV = NdotL * sqrt(NdotV * NdotV * (1.0 - a2) + a2);
4814 float GGXL = NdotV * sqrt(NdotL * NdotL * (1.0 - a2) + a2);
4815 return 0.5 / max(GGXV + GGXL, 1e-5);
4816}
4817float D_GGX(float NdotH, float roughness) {
4818 float a2 = roughness * roughness;
4819 float f = (NdotH * a2 - NdotH) * NdotH + 1.0;
4820 return a2 / (PI * f * f);
4821}
4822vec3 eval_pbr(vec3 N, vec3 V, vec3 L, vec3 albedo, float roughness, float metalness) {
4823 vec3 H = normalize(V + L);
4824 float NdotV = max(dot(N, V), 0.0);
4825 float NdotL = max(dot(N, L), 0.0);
4826 float NdotH = max(dot(N, H), 0.0);
4827 float LdotH = max(dot(L, H), 0.0);
4828 vec3 f0 = mix(vec3(0.04), albedo, metalness);
4829 float D = D_GGX(NdotH, roughness);
4830 float V_vis = V_SmithGGXCorrelated(NdotV, NdotL, roughness);
4831 vec3 F = F_Schlick(f0, 1.0, LdotH);
4832 vec3 Fr = D * V_vis * F;
4833 vec3 Fd = (1.0 - F) * (1.0 - metalness) * albedo / PI;
4834 return (Fd + Fr) * NdotL;
4835}
4836"#
4837}
4838
4839pub fn glsl_shadow_functions() -> &'static str {
4840 r#"
4841float shadow_hard(sampler2DShadow shadowMap, vec4 shadowCoord) {
4842 vec3 projCoords = shadowCoord.xyz / shadowCoord.w;
4843 return texture(shadowMap, projCoords);
4844}
4845float shadow_soft_pcf(sampler2DShadow shadowMap, vec4 shadowCoord, int samples) {
4846 vec2 texelSize = 1.0 / textureSize(shadowMap, 0);
4847 vec3 projCoords = shadowCoord.xyz / shadowCoord.w;
4848 float shadow = 0.0;
4849 float total = 0.0;
4850 for(int x = -samples; x <= samples; x++) {
4851 for(int y = -samples; y <= samples; y++) {
4852 vec3 offset = vec3(vec2(x, y) * texelSize, 0.0);
4853 shadow += texture(shadowMap, projCoords + offset);
4854 total += 1.0;
4855 }
4856 }
4857 return shadow / total;
4858}
4859vec2 poisson_disk[16] = vec2[](
4860 vec2(-0.94201624, -0.39906216), vec2(0.94558609, -0.76890725),
4861 vec2(-0.094184101, -0.92938870), vec2(0.34495938, 0.29387760),
4862 vec2(-0.91588581, 0.45771432), vec2(-0.81544232, -0.87912464),
4863 vec2(-0.38277543, 0.27676845), vec2(0.97484398, 0.75648379),
4864 vec2(0.44323325, -0.97511554), vec2(0.53742981, -0.47373420),
4865 vec2(-0.26496911, -0.41893023), vec2(0.79197514, 0.19090188),
4866 vec2(-0.24188840, 0.99706507), vec2(-0.81409955, 0.91437590),
4867 vec2(0.19984126, 0.78641367), vec2(0.14383161, -0.14100790)
4868);
4869float shadow_poisson(sampler2DShadow shadowMap, vec4 shadowCoord, float radius) {
4870 vec3 projCoords = shadowCoord.xyz / shadowCoord.w;
4871 float shadow = 0.0;
4872 for(int i = 0; i < 16; i++) {
4873 vec2 offset = poisson_disk[i] * radius;
4874 shadow += texture(shadowMap, projCoords + vec3(offset, 0.0));
4875 }
4876 return shadow / 16.0;
4877}
4878"#
4879}
4880
4881pub fn glsl_atmosphere() -> &'static str {
4882 r#"
4883// Simple atmospheric scattering approximation
4884vec3 atmosphere(vec3 ray_dir, vec3 sun_dir, vec3 sun_color) {
4885 float sun_dot = max(dot(ray_dir, sun_dir), 0.0);
4886 vec3 sky_color = vec3(0.1, 0.3, 0.7);
4887 vec3 horizon_color = vec3(0.7, 0.5, 0.3);
4888 float horizon_blend = pow(1.0 - abs(ray_dir.y), 4.0);
4889 vec3 sky = mix(sky_color, horizon_color, horizon_blend);
4890 float sun_disc = smoothstep(0.998, 1.0, sun_dot);
4891 float halo = pow(sun_dot, 8.0) * 0.3;
4892 return sky + sun_color * (sun_disc + halo);
4893}
4894vec3 fog(vec3 color, float depth, vec3 fog_color, float fog_start, float fog_end) {
4895 float factor = clamp((depth - fog_start) / (fog_end - fog_start), 0.0, 1.0);
4896 return mix(color, fog_color, factor);
4897}
4898vec3 exponential_fog(vec3 color, float depth, vec3 fog_color, float density) {
4899 float factor = 1.0 - exp(-density * depth);
4900 return mix(color, fog_color, factor);
4901}
4902"#
4903}
4904
4905pub fn per_frame_cbuffer_glsl() -> &'static str {
4908 r#"
4909layout(std140, binding = 0) uniform PerFrame {
4910 mat4 view;
4911 mat4 proj;
4912 mat4 view_proj;
4913 mat4 inv_view;
4914 mat4 inv_proj;
4915 vec4 camera_pos;
4916 vec4 camera_dir;
4917 vec4 viewport_size;
4918 float time;
4919 float delta_time;
4920 float near_plane;
4921 float far_plane;
4922 vec4 sun_direction;
4923 vec4 sun_color;
4924 vec4 ambient_color;
4925 vec2 jitter_offset;
4926 uint frame_index;
4927 float exposure;
4928} u_frame;
4929"#
4930}
4931
4932pub fn per_object_cbuffer_glsl() -> &'static str {
4933 r#"
4934layout(std140, binding = 1) uniform PerObject {
4935 mat4 model;
4936 mat4 model_view;
4937 mat4 mvp;
4938 mat4 normal_matrix;
4939 vec4 object_color;
4940 vec4 object_id;
4941 float lod_bias;
4942 uint material_flags;
4943 float opacity;
4944 float pad;
4945} u_object;
4946"#
4947}
4948
4949pub fn per_material_cbuffer_glsl() -> &'static str {
4950 r#"
4951layout(std140, binding = 2) uniform PerMaterial {
4952 vec4 albedo_color;
4953 vec4 emissive_color;
4954 float roughness;
4955 float metalness;
4956 float normal_scale;
4957 float occlusion_strength;
4958 float emissive_intensity;
4959 float opacity;
4960 float alpha_cutoff;
4961 uint flags;
4962 vec4 uv_transform;
4963} u_material;
4964"#
4965}
4966
4967pub const SHADER_BUILTIN_SNIPPETS: usize = 11;
4970pub const SHADER_GLSL_BUILTIN_FUNCTIONS: usize = 28;
4971pub const SHADER_CBUFFER_BINDINGS: u32 = 3;
4972pub const SHADER_ATMOSPHERE_SAMPLES: u32 = 8;
4973pub const SHADER_SHADOW_PCF_SAMPLES: u32 = 9;
4974pub const SHADER_SHADOW_POISSON_SAMPLES: u32 = 16;
4975pub const GLSL_PRECISION_HIGHP: &str = "highp";
4976pub const GLSL_PRECISION_MEDIUMP: &str = "mediump";
4977pub const GLSL_PRECISION_LOWP: &str = "lowp";
4978
4979pub fn all_glsl_builtin_types() -> &'static [&'static str] {
4980 &[ "void", "bool", "int", "uint", "float", "double",
4981 "bvec2", "bvec3", "bvec4", "ivec2", "ivec3", "ivec4",
4982 "uvec2", "uvec3", "uvec4", "vec2", "vec3", "vec4",
4983 "dvec2", "dvec3", "dvec4",
4984 "mat2", "mat3", "mat4", "mat2x3", "mat2x4", "mat3x2",
4985 "mat3x4", "mat4x2", "mat4x3",
4986 "sampler2D", "sampler3D", "samplerCube", "sampler2DArray",
4987 "sampler2DShadow", "samplerCubeShadow",
4988 "isampler2D", "usampler2D", "image2D", "imageCube" ]
4989}
4990
4991pub fn all_glsl_keywords() -> &'static [&'static str] {
4992 &[ "if", "else", "for", "while", "do", "switch", "case", "default",
4993 "break", "continue", "return", "discard",
4994 "void", "struct", "precision", "highp", "mediump", "lowp",
4995 "in", "out", "inout", "uniform", "const", "layout",
4996 "attribute", "varying", "flat", "smooth", "centroid",
4997 "noperspective", "invariant", "coherent", "volatile",
4998 "restrict", "readonly", "writeonly" ]
4999}
5000
5001
5002#[derive(Clone, Debug)]
5005pub struct RenderFeatureFlags {
5006 pub pbr: bool,
5007 pub shadows: bool,
5008 pub ssao: bool,
5009 pub ssr: bool,
5010 pub bloom: bool,
5011 pub fxaa: bool,
5012 pub taa: bool,
5013 pub dof: bool,
5014 pub motion_blur: bool,
5015 pub volumetric_fog: bool,
5016 pub lens_flare: bool,
5017 pub chromatic_aberration: bool,
5018 pub vignette: bool,
5019 pub color_grading: bool,
5020 pub debug_mode: bool,
5021}
5022
5023impl RenderFeatureFlags {
5024 pub fn all_off() -> Self { Self { pbr: false, shadows: false, ssao: false, ssr: false, bloom: false, fxaa: false, taa: false, dof: false, motion_blur: false, volumetric_fog: false, lens_flare: false, chromatic_aberration: false, vignette: false, color_grading: false, debug_mode: false } }
5025 pub fn default_high_quality() -> Self { Self { pbr: true, shadows: true, ssao: true, ssr: false, bloom: true, fxaa: true, taa: true, dof: false, motion_blur: false, volumetric_fog: false, lens_flare: false, chromatic_aberration: false, vignette: true, color_grading: true, debug_mode: false } }
5026 pub fn default_medium_quality() -> Self { Self { pbr: true, shadows: true, ssao: false, ssr: false, bloom: true, fxaa: true, taa: false, ..Self::all_off() } }
5027 pub fn default_low_quality() -> Self { Self { fxaa: true, ..Self::all_off() } }
5028 pub fn active_count(&self) -> usize {
5029 [self.pbr, self.shadows, self.ssao, self.ssr, self.bloom, self.fxaa, self.taa, self.dof, self.motion_blur, self.volumetric_fog, self.lens_flare, self.chromatic_aberration, self.vignette, self.color_grading].iter().filter(|&&b| b).count()
5030 }
5031 pub fn generate_defines(&self) -> Vec<(String, String)> {
5032 let mut defines = Vec::new();
5033 if self.pbr { defines.push(("USE_PBR".into(), "1".into())); }
5034 if self.shadows { defines.push(("USE_SHADOWS".into(), "1".into())); }
5035 if self.ssao { defines.push(("USE_SSAO".into(), "1".into())); }
5036 if self.bloom { defines.push(("USE_BLOOM".into(), "1".into())); }
5037 if self.fxaa { defines.push(("USE_FXAA".into(), "1".into())); }
5038 if self.taa { defines.push(("USE_TAA".into(), "1".into())); }
5039 defines
5040 }
5041}
5042
5043impl Default for RenderFeatureFlags {
5044 fn default() -> Self { Self::default_high_quality() }
5045}
5046
5047#[derive(Clone, Debug)]
5050pub struct ShaderCompilationContext {
5051 pub source_path: String,
5052 pub stage: ShaderStage,
5053 pub target: CodeGenTarget,
5054 pub defines: HashMap<String, String>,
5055 pub include_paths: Vec<String>,
5056 pub optimization: OptimizationLevel,
5057 pub version: String,
5058 pub entry_point: String,
5059 pub features: RenderFeatureFlags,
5060}
5061
5062impl ShaderCompilationContext {
5063 pub fn new(path: impl Into<String>, stage: ShaderStage, target: CodeGenTarget) -> Self {
5064 Self { source_path: path.into(), stage, target, defines: HashMap::new(), include_paths: Vec::new(), optimization: OptimizationLevel::Medium, version: "450".into(), entry_point: "main".into(), features: RenderFeatureFlags::default() }
5065 }
5066 pub fn add_define(&mut self, k: impl Into<String>, v: impl Into<String>) { self.defines.insert(k.into(), v.into()); }
5067 pub fn apply_features(&mut self) {
5068 for (k, v) in self.features.generate_defines() { self.defines.insert(k, v); }
5069 }
5070 pub fn is_vertex(&self) -> bool { self.stage == ShaderStage::Vertex }
5071 pub fn is_fragment(&self) -> bool { self.stage == ShaderStage::Fragment }
5072 pub fn is_compute(&self) -> bool { self.stage == ShaderStage::Compute }
5073}
5074
5075#[derive(Clone, Debug)]
5078pub struct ShaderBundle {
5079 pub id: u32,
5080 pub name: String,
5081 pub programs: Vec<ShaderProgram>,
5082 pub materials: Vec<Material>,
5083 pub templates: Vec<ShaderTemplate>,
5084 pub version: u32,
5085 pub description: String,
5086 pub author: String,
5087 pub created_at: u64,
5088}
5089
5090impl ShaderBundle {
5091 pub fn new(id: u32, name: impl Into<String>) -> Self {
5092 Self { id, name: name.into(), programs: Vec::new(), materials: Vec::new(), templates: Vec::new(), version: 1, description: String::new(), author: String::new(), created_at: 0 }
5093 }
5094 pub fn add_program(&mut self, p: ShaderProgram) { self.programs.push(p); }
5095 pub fn add_material(&mut self, m: Material) { self.materials.push(m); }
5096 pub fn add_template(&mut self, t: ShaderTemplate) { self.templates.push(t); }
5097 pub fn program_count(&self) -> usize { self.programs.len() }
5098 pub fn material_count(&self) -> usize { self.materials.len() }
5099 pub fn find_program(&self, name: &str) -> Option<&ShaderProgram> { self.programs.iter().find(|p| p.name == name) }
5100 pub fn is_empty(&self) -> bool { self.programs.is_empty() && self.materials.is_empty() }
5101}
5102
5103#[derive(Clone, Debug)]
5104pub struct ShaderBundleRegistry {
5105 pub bundles: HashMap<u32, ShaderBundle>,
5106 pub next_id: u32,
5107}
5108
5109impl ShaderBundleRegistry {
5110 pub fn new() -> Self { Self { bundles: HashMap::new(), next_id: 1 } }
5111 pub fn register(&mut self, mut b: ShaderBundle) -> u32 {
5112 let id = self.next_id; self.next_id += 1;
5113 b.id = id;
5114 self.bundles.insert(id, b);
5115 id
5116 }
5117 pub fn get(&self, id: u32) -> Option<&ShaderBundle> { self.bundles.get(&id) }
5118 pub fn find_by_name(&self, name: &str) -> Option<&ShaderBundle> { self.bundles.values().find(|b| b.name == name) }
5119 pub fn total_programs(&self) -> usize { self.bundles.values().map(|b| b.program_count()).sum() }
5120 pub fn count(&self) -> usize { self.bundles.len() }
5121}
5122
5123impl Default for ShaderBundleRegistry {
5124 fn default() -> Self { Self::new() }
5125}
5126
5127#[derive(Clone, Debug)]
5130pub struct ShaderWorkspace {
5131 pub registry: ShaderProgramRegistry,
5132 pub cache: ShaderCache,
5133 pub library: ShaderLibrary,
5134 pub material_lib: MaterialLibrary,
5135 pub texture_reg: TextureRegistry,
5136 pub buffer_reg: BufferRegistry,
5137 pub pipeline: RenderPipeline,
5138 pub post_process: PostProcessStack,
5139 pub template_lib: ShaderTemplateLibrary,
5140 pub linter: ShaderLinter,
5141 pub formatter: ShaderFormatter,
5142 pub debugger: ShaderDebugger,
5143 pub profiler: ShaderProfiler,
5144 pub hot_reload: HotReloadManager,
5145 pub editor_state: ShaderEditorState,
5146 pub autocomplete: AutoCompleteProvider,
5147 pub linker: ShaderLinker,
5148 pub bundle_reg: ShaderBundleRegistry,
5149 pub feature_flags: RenderFeatureFlags,
5150 pub memory_budget: GpuMemoryBudget,
5151 pub render_stats: RenderStatistics,
5152}
5153
5154impl ShaderWorkspace {
5155 pub fn new() -> Self {
5156 Self {
5157 registry: ShaderProgramRegistry::new(),
5158 cache: ShaderCache::new(SHADER_CACHE_SIZE),
5159 library: ShaderLibrary::build_standard(),
5160 material_lib: MaterialLibrary::new(),
5161 texture_reg: TextureRegistry::new(),
5162 buffer_reg: BufferRegistry::new(),
5163 pipeline: RenderPipeline::new(),
5164 post_process: PostProcessStack::new(),
5165 template_lib: ShaderTemplateLibrary::new(),
5166 linter: ShaderLinter::new(),
5167 formatter: ShaderFormatter::new(),
5168 debugger: ShaderDebugger::build_defaults(),
5169 profiler: ShaderProfiler::new(),
5170 hot_reload: HotReloadManager::new(),
5171 editor_state: ShaderEditorState::new(),
5172 autocomplete: AutoCompleteProvider::new(),
5173 linker: ShaderLinker::new(),
5174 bundle_reg: ShaderBundleRegistry::new(),
5175 feature_flags: RenderFeatureFlags::default(),
5176 memory_budget: GpuMemoryBudget::new(GPU_MEMORY_BUDGET_DEFAULT_MB * 1024 * 1024),
5177 render_stats: RenderStatistics::new(),
5178 }
5179 }
5180 pub fn program_count(&self) -> usize { self.registry.count() }
5181 pub fn material_count(&self) -> usize { self.material_lib.count() }
5182 pub fn texture_count(&self) -> usize { self.texture_reg.count() }
5183 pub fn buffer_count(&self) -> usize { self.buffer_reg.count() }
5184 pub fn begin_frame(&mut self) { self.render_stats.begin_frame(); self.profiler.begin_frame(); }
5185 pub fn has_program(&self, name: &str) -> bool { self.registry.find_by_name(name).is_some() }
5186 pub fn tick(&mut self, dt: f32) { let _ = self.hot_reload.tick(dt); }
5187}
5188
5189impl Default for ShaderWorkspace {
5190 fn default() -> Self { Self::new() }
5191}
5192
5193pub const SHADER_BUNDLE_MAX: usize = 64;
5196pub const SHADER_WORKSPACE_MAX_PROGRAMS: usize = SHADER_MAX_PROGRAMS;
5197pub const RENDER_FEATURE_COUNT: usize = 15;
5198pub const SHADER_VERSION_DEFAULT: &str = "450";
5199pub const SHADER_ES_VERSION_DEFAULT: &str = "300 es";
5200pub const SHADER_ENTRY_POINT_DEFAULT: &str = "main";
5201pub const GLSL_BUILTIN_TYPE_COUNT: usize = 40;
5202pub const GLSL_KEYWORD_COUNT: usize = 31;
5203pub const GLSL_MAX_FRAGMENT_OUTPUTS: u32 = 8;
5204pub const GLSL_MAX_VERTEX_ATTRIBS: u32 = 32;
5205
5206pub fn build_standard_shader_workspace() -> ShaderWorkspace { ShaderWorkspace::new() }
5207pub fn shader_workspace_summary(ws: &ShaderWorkspace) -> String {
5208 format!("ShaderWorkspace: {} programs, {} materials, {} textures, {} buffers, {} render passes", ws.program_count(), ws.material_count(), ws.texture_count(), ws.buffer_count(), ws.pipeline.pass_count())
5209}
5210pub fn shader_system_version() -> &'static str { "1.0.0" }
5211pub fn is_shader_system_ready() -> bool { true }
5212
5213
5214#[derive(Clone, Debug)]
5217pub struct RenderGraphNode {
5218 pub id: u32,
5219 pub name: String,
5220 pub node_type: RenderGraphNodeType,
5221 pub inputs: Vec<RenderGraphResource>,
5222 pub outputs: Vec<RenderGraphResource>,
5223 pub shader_id: Option<u32>,
5224 pub enabled: bool,
5225 pub order: u32,
5226 pub async_compute: bool,
5227}
5228
5229#[derive(Clone, Debug, PartialEq)]
5230pub enum RenderGraphNodeType { Pass, Blit, Compute, Present, Upload, Barrier, Custom(String) }
5231
5232#[derive(Clone, Debug)]
5233pub struct RenderGraphResource {
5234 pub name: String,
5235 pub resource_type: RenderGraphResourceType,
5236 pub format: AttachmentFormat,
5237 pub width: u32,
5238 pub height: u32,
5239 pub mips: u32,
5240 pub transient: bool,
5241}
5242
5243#[derive(Clone, Debug, PartialEq)]
5244pub enum RenderGraphResourceType { Texture, Buffer, RenderTarget, DepthStencil }
5245
5246impl RenderGraphNode {
5247 pub fn new(id: u32, name: impl Into<String>, node_type: RenderGraphNodeType) -> Self {
5248 Self { id, name: name.into(), node_type, inputs: Vec::new(), outputs: Vec::new(), shader_id: None, enabled: true, order: 0, async_compute: false }
5249 }
5250 pub fn add_input(&mut self, res: RenderGraphResource) { self.inputs.push(res); }
5251 pub fn add_output(&mut self, res: RenderGraphResource) { self.outputs.push(res); }
5252 pub fn is_pass(&self) -> bool { self.node_type == RenderGraphNodeType::Pass }
5253 pub fn is_compute(&self) -> bool { self.node_type == RenderGraphNodeType::Compute }
5254 pub fn input_count(&self) -> usize { self.inputs.len() }
5255 pub fn output_count(&self) -> usize { self.outputs.len() }
5256 pub fn with_shader(mut self, id: u32) -> Self { self.shader_id = Some(id); self }
5257}
5258
5259#[derive(Clone, Debug)]
5260pub struct RenderGraph {
5261 pub nodes: Vec<RenderGraphNode>,
5262 pub edges: Vec<(u32, u32)>,
5263 pub name: String,
5264 pub next_id: u32,
5265}
5266
5267impl RenderGraph {
5268 pub fn new(name: impl Into<String>) -> Self { Self { nodes: Vec::new(), edges: Vec::new(), name: name.into(), next_id: 1 } }
5269 pub fn add_node(&mut self, node_type: RenderGraphNodeType, name: impl Into<String>) -> u32 {
5270 let id = self.next_id; self.next_id += 1;
5271 self.nodes.push(RenderGraphNode::new(id, name, node_type));
5272 id
5273 }
5274 pub fn connect(&mut self, from: u32, to: u32) { self.edges.push((from, to)); }
5275 pub fn sorted_nodes(&self) -> Vec<&RenderGraphNode> {
5276 let mut sorted: Vec<_> = self.nodes.iter().filter(|n| n.enabled).collect();
5277 sorted.sort_by_key(|n| n.order);
5278 sorted
5279 }
5280 pub fn node_count(&self) -> usize { self.nodes.len() }
5281 pub fn find_node(&self, id: u32) -> Option<&RenderGraphNode> { self.nodes.iter().find(|n| n.id == id) }
5282 pub fn remove_node(&mut self, id: u32) { self.nodes.retain(|n| n.id != id); self.edges.retain(|(a, b)| *a != id && *b != id); }
5283}
5284
5285impl Default for RenderGraph {
5286 fn default() -> Self { Self::new("default") }
5287}
5288
5289#[derive(Clone, Debug)]
5292pub struct ShaderCompilerMain {
5293 pub workspace: ShaderWorkspace,
5294 pub render_graph: RenderGraph,
5295 pub compile_stats: CompileStats,
5296}
5297
5298#[derive(Clone, Debug, Default)]
5299pub struct CompileStats {
5300 pub total_compiled: u32,
5301 pub total_failed: u32,
5302 pub total_cached: u32,
5303 pub total_reloaded: u32,
5304 pub avg_compile_ms: f32,
5305 pub peak_compile_ms: f32,
5306}
5307
5308impl CompileStats {
5309 pub fn new() -> Self { Self::default() }
5310 pub fn record_compile(&mut self, ms: f32, success: bool, from_cache: bool) {
5311 if from_cache { self.total_cached += 1; return; }
5312 if success { self.total_compiled += 1; } else { self.total_failed += 1; }
5313 self.peak_compile_ms = self.peak_compile_ms.max(ms);
5314 let total = self.total_compiled + self.total_failed;
5315 self.avg_compile_ms = (self.avg_compile_ms * (total - 1) as f32 + ms) / total as f32;
5316 }
5317 pub fn success_rate(&self) -> f32 {
5318 let total = self.total_compiled + self.total_failed;
5319 if total == 0 { 1.0 } else { self.total_compiled as f32 / total as f32 }
5320 }
5321 pub fn total_attempts(&self) -> u32 { self.total_compiled + self.total_failed + self.total_cached }
5322}
5323
5324impl ShaderCompilerMain {
5325 pub fn new() -> Self { Self { workspace: ShaderWorkspace::new(), render_graph: RenderGraph::new("main"), compile_stats: CompileStats::new() } }
5326 pub fn add_program(&mut self, prog: ShaderProgram) -> u32 { self.workspace.registry.add(prog) }
5327 pub fn get_program(&self, id: u32) -> Option<&ShaderProgram> { self.workspace.registry.get(id) }
5328 pub fn add_material(&mut self, mat: Material) -> u32 { self.workspace.material_lib.add(mat) }
5329 pub fn add_texture(&mut self, tex: TextureDescriptor) -> u32 { self.workspace.texture_reg.register(tex) }
5330 pub fn begin_frame(&mut self) { self.workspace.begin_frame(); }
5331 pub fn tick(&mut self, dt: f32) { self.workspace.tick(dt); }
5332 pub fn program_count(&self) -> usize { self.workspace.program_count() }
5333 pub fn compilation_summary(&self) -> String {
5334 format!("Compiled: {}, Failed: {}, Cached: {}, Success: {:.1}%", self.compile_stats.total_compiled, self.compile_stats.total_failed, self.compile_stats.total_cached, self.compile_stats.success_rate() * 100.0)
5335 }
5336}
5337
5338impl Default for ShaderCompilerMain {
5339 fn default() -> Self { Self::new() }
5340}
5341
5342#[derive(Clone, Debug)]
5345pub struct ColorGradingSettings {
5346 pub exposure: f32,
5347 pub contrast: f32,
5348 pub saturation: f32,
5349 pub brightness: f32,
5350 pub hue_shift: f32,
5351 pub lift: [f32; 3],
5352 pub gamma: [f32; 3],
5353 pub gain: [f32; 3],
5354 pub white_balance_temp: f32,
5355 pub white_balance_tint: f32,
5356 pub shadows_color: [f32; 3],
5357 pub midtones_color: [f32; 3],
5358 pub highlights_color: [f32; 3],
5359 pub tonemapping: ToneMappingMode,
5360}
5361
5362#[derive(Clone, Debug, PartialEq)]
5363pub enum ToneMappingMode { None, Reinhard, Aces, Filmic, Uncharted2, Custom }
5364
5365impl Default for ColorGradingSettings {
5366 fn default() -> Self {
5367 Self { exposure: 0.0, contrast: 1.0, saturation: 1.0, brightness: 0.0, hue_shift: 0.0, lift: [0.0; 3], gamma: [1.0; 3], gain: [1.0; 3], white_balance_temp: 6500.0, white_balance_tint: 0.0, shadows_color: [0.0; 3], midtones_color: [0.0; 3], highlights_color: [0.0; 3], tonemapping: ToneMappingMode::Aces }
5368 }
5369}
5370
5371impl ColorGradingSettings {
5372 pub fn new() -> Self { Self::default() }
5373 pub fn neutral() -> Self { Self::default() }
5374 pub fn warm() -> Self { Self { white_balance_temp: 7500.0, saturation: 1.1, ..Self::default() } }
5375 pub fn cool() -> Self { Self { white_balance_temp: 5500.0, saturation: 0.9, ..Self::default() } }
5376 pub fn cinematic() -> Self { Self { contrast: 1.15, saturation: 0.9, tonemapping: ToneMappingMode::Filmic, ..Self::default() } }
5377 pub fn generate_glsl(&self) -> String {
5378 format!("// ColorGrading: exposure={:.2}, contrast={:.2}, saturation={:.2}, tonemapping={:?}", self.exposure, self.contrast, self.saturation, self.tonemapping)
5379 }
5380 pub fn is_neutral(&self) -> bool { self.exposure == 0.0 && self.contrast == 1.0 && self.saturation == 1.0 }
5381}
5382
5383#[derive(Clone, Debug)]
5386pub struct SsaoConfig {
5387 pub kernel_size: u32,
5388 pub radius: f32,
5389 pub bias: f32,
5390 pub power: f32,
5391 pub blur_passes: u32,
5392 pub enabled: bool,
5393 pub quality: SsaoQuality,
5394}
5395
5396#[derive(Clone, Debug, PartialEq)]
5397pub enum SsaoQuality { Low, Medium, High, Ultra }
5398
5399impl SsaoConfig {
5400 pub fn new() -> Self { Self { kernel_size: 32, radius: 0.5, bias: 0.025, power: 2.0, blur_passes: 2, enabled: true, quality: SsaoQuality::Medium } }
5401 pub fn low() -> Self { Self { kernel_size: 8, radius: 0.3, blur_passes: 1, quality: SsaoQuality::Low, ..Self::new() } }
5402 pub fn high() -> Self { Self { kernel_size: 64, radius: 0.8, blur_passes: 3, quality: SsaoQuality::High, ..Self::new() } }
5403 pub fn ultra() -> Self { Self { kernel_size: 128, radius: 1.0, blur_passes: 4, quality: SsaoQuality::Ultra, ..Self::new() } }
5404}
5405
5406impl Default for SsaoConfig {
5407 fn default() -> Self { Self::new() }
5408}
5409
5410#[derive(Clone, Debug)]
5413pub struct DepthOfFieldConfig {
5414 pub enabled: bool,
5415 pub focus_distance: f32,
5416 pub focus_range: f32,
5417 pub far_blur_range: f32,
5418 pub near_blur_range: f32,
5419 pub bokeh_radius: f32,
5420 pub bokeh_blade_count: u32,
5421 pub bokeh_rotation: f32,
5422 pub max_coc_radius: f32,
5423}
5424
5425impl DepthOfFieldConfig {
5426 pub fn new() -> Self { Self { enabled: false, focus_distance: 10.0, focus_range: 5.0, far_blur_range: 20.0, near_blur_range: 2.0, bokeh_radius: 8.0, bokeh_blade_count: 6, bokeh_rotation: 0.0, max_coc_radius: 20.0 } }
5427 pub fn portrait() -> Self { Self { enabled: true, focus_distance: 2.0, focus_range: 0.5, bokeh_radius: 12.0, ..Self::new() } }
5428 pub fn cinematic() -> Self { Self { enabled: true, focus_distance: 5.0, focus_range: 2.0, bokeh_radius: 10.0, bokeh_blade_count: 8, ..Self::new() } }
5429}
5430
5431impl Default for DepthOfFieldConfig {
5432 fn default() -> Self { Self::new() }
5433}
5434
5435#[derive(Clone, Debug)]
5438pub struct BloomConfig {
5439 pub enabled: bool,
5440 pub threshold: f32,
5441 pub intensity: f32,
5442 pub scatter: f32,
5443 pub clamp: f32,
5444 pub downsample_passes: u32,
5445 pub mode: BloomMode,
5446 pub tint: [f32; 3],
5447}
5448
5449#[derive(Clone, Debug, PartialEq)]
5450pub enum BloomMode { Classic, Kawase, Dual, Convolution }
5451
5452impl BloomConfig {
5453 pub fn new() -> Self { Self { enabled: true, threshold: 0.9, intensity: 0.5, scatter: 0.7, clamp: 65472.0, downsample_passes: 5, mode: BloomMode::Dual, tint: [1.0; 3] } }
5454 pub fn subtle() -> Self { Self { intensity: 0.2, ..Self::new() } }
5455 pub fn intense() -> Self { Self { intensity: 1.5, threshold: 0.7, ..Self::new() } }
5456 pub fn disabled() -> Self { Self { enabled: false, ..Self::new() } }
5457}
5458
5459impl Default for BloomConfig {
5460 fn default() -> Self { Self::new() }
5461}
5462
5463pub const RENDER_GRAPH_MAX_NODES: usize = 64;
5466pub const RENDER_GRAPH_MAX_EDGES: usize = 256;
5467pub const COLOR_GRADING_LUT_SIZE: u32 = 32;
5468pub const SSAO_KERNEL_SIZE_MAX: u32 = 256;
5469pub const DOF_MAX_COC_RADIUS: f32 = 32.0;
5470pub const BLOOM_MAX_DOWNSAMPLE_PASSES: u32 = 8;
5471pub const SHADER_COMPILER_MAX_WORKSPACES: usize = 8;
5472pub const GPU_BUDGET_WARNING_PERCENT: f32 = 80.0;
5473pub const GPU_BUDGET_CRITICAL_PERCENT: f32 = 95.0;
5474pub const SHADER_PERMUTATION_HASH_SEED: u64 = 14695981039346656037;
5475
5476pub fn tonemap_name(mode: &ToneMappingMode) -> &'static str {
5477 match mode { ToneMappingMode::None => "None", ToneMappingMode::Reinhard => "Reinhard", ToneMappingMode::Aces => "ACES", ToneMappingMode::Filmic => "Filmic", ToneMappingMode::Uncharted2 => "Uncharted 2", ToneMappingMode::Custom => "Custom" }
5478}
5479pub fn bloom_mode_name(mode: &BloomMode) -> &'static str {
5480 match mode { BloomMode::Classic => "Classic", BloomMode::Kawase => "Kawase", BloomMode::Dual => "Dual Kawase", BloomMode::Convolution => "Convolution" }
5481}
5482pub fn ssao_quality_name(q: &SsaoQuality) -> &'static str {
5483 match q { SsaoQuality::Low => "Low", SsaoQuality::Medium => "Medium", SsaoQuality::High => "High", SsaoQuality::Ultra => "Ultra" }
5484}
5485pub fn shader_compiler_build_date() -> &'static str { "2026-03-29" }
5486pub fn shader_compiler_complete_info() -> String {
5487 format!("ShaderCompilerEditor v{} — {} modules, render graph, materials, post-process, color grading", shader_system_version(), shader_module_count())
5488}
5489
5490
5491#[derive(Clone, Debug)]
5494pub struct SsrConfig {
5495 pub enabled: bool,
5496 pub max_steps: u32,
5497 pub max_distance: f32,
5498 pub thickness: f32,
5499 pub stride: u32,
5500 pub jitter: f32,
5501 pub fade_start: f32,
5502 pub fade_end: f32,
5503 pub reflection_blend: f32,
5504 pub quality: SsrQuality,
5505}
5506
5507#[derive(Clone, Debug, PartialEq)]
5508pub enum SsrQuality { Low, Medium, High }
5509
5510impl SsrConfig {
5511 pub fn new() -> Self { Self { enabled: false, max_steps: 32, max_distance: 100.0, thickness: 0.5, stride: 2, jitter: 0.5, fade_start: 0.7, fade_end: 1.0, reflection_blend: 0.5, quality: SsrQuality::Medium } }
5512 pub fn high() -> Self { Self { enabled: true, max_steps: 64, stride: 1, quality: SsrQuality::High, ..Self::new() } }
5513 pub fn low() -> Self { Self { enabled: true, max_steps: 16, stride: 4, quality: SsrQuality::Low, ..Self::new() } }
5514}
5515
5516impl Default for SsrConfig {
5517 fn default() -> Self { Self::new() }
5518}
5519
5520#[derive(Clone, Debug)]
5523pub struct TaaConfig {
5524 pub enabled: bool,
5525 pub blend_factor: f32,
5526 pub history_blend: f32,
5527 pub sharpness: f32,
5528 pub jitter_scale: f32,
5529 pub motion_rejection: f32,
5530 pub clip_aabb: bool,
5531 pub variance_clip_gamma: f32,
5532 pub sample_count: u32,
5533}
5534
5535impl TaaConfig {
5536 pub fn new() -> Self { Self { enabled: true, blend_factor: 0.1, history_blend: 0.9, sharpness: 0.5, jitter_scale: 1.0, motion_rejection: 0.3, clip_aabb: true, variance_clip_gamma: 1.0, sample_count: 8 } }
5537 pub fn aggressive() -> Self { Self { blend_factor: 0.05, motion_rejection: 0.5, ..Self::new() } }
5538 pub fn gentle() -> Self { Self { blend_factor: 0.2, motion_rejection: 0.1, ..Self::new() } }
5539}
5540
5541impl Default for TaaConfig {
5542 fn default() -> Self { Self::new() }
5543}
5544
5545#[derive(Clone, Debug)]
5548pub struct VolumetricFogConfig {
5549 pub enabled: bool,
5550 pub density: f32,
5551 pub scattering: f32,
5552 pub absorption: f32,
5553 pub anisotropy: f32,
5554 pub height_falloff: f32,
5555 pub base_height: f32,
5556 pub fog_color: [f32; 3],
5557 pub ambient_fog_color: [f32; 3],
5558 pub light_intensity: f32,
5559 pub num_slices: u32,
5560 pub temporal_reprojection: bool,
5561}
5562
5563impl VolumetricFogConfig {
5564 pub fn new() -> Self { Self { enabled: false, density: 0.1, scattering: 0.5, absorption: 0.1, anisotropy: 0.5, height_falloff: 0.1, base_height: 0.0, fog_color: [1.0, 0.95, 0.9], ambient_fog_color: [0.5, 0.6, 0.8], light_intensity: 1.0, num_slices: 128, temporal_reprojection: true } }
5565 pub fn light_fog() -> Self { Self { enabled: true, density: 0.02, ..Self::new() } }
5566 pub fn heavy_fog() -> Self { Self { enabled: true, density: 0.3, ..Self::new() } }
5567 pub fn night_fog() -> Self { Self { enabled: true, density: 0.15, fog_color: [0.3, 0.35, 0.5], ambient_fog_color: [0.1, 0.1, 0.2], ..Self::new() } }
5568}
5569
5570impl Default for VolumetricFogConfig {
5571 fn default() -> Self { Self::new() }
5572}
5573
5574#[derive(Clone, Debug)]
5577pub struct LensFlareConfig {
5578 pub enabled: bool,
5579 pub intensity: f32,
5580 pub threshold: f32,
5581 pub ghosts: u32,
5582 pub ghost_dispersal: f32,
5583 pub halo_width: f32,
5584 pub chromatic_distortion: f32,
5585 pub starburst_intensity: f32,
5586 pub starburst_count: u32,
5587}
5588
5589impl LensFlareConfig {
5590 pub fn new() -> Self { Self { enabled: false, intensity: 0.5, threshold: 0.8, ghosts: 4, ghost_dispersal: 0.4, halo_width: 0.4, chromatic_distortion: 5.0, starburst_intensity: 0.5, starburst_count: 8 } }
5591 pub fn subtle() -> Self { Self { enabled: true, intensity: 0.2, ..Self::new() } }
5592 pub fn dramatic() -> Self { Self { enabled: true, intensity: 1.5, ghosts: 8, ..Self::new() } }
5593}
5594
5595impl Default for LensFlareConfig {
5596 fn default() -> Self { Self::new() }
5597}
5598
5599#[derive(Clone, Debug)]
5602pub struct FullPostProcessConfig {
5603 pub bloom: BloomConfig,
5604 pub color_grading: ColorGradingSettings,
5605 pub ssao: SsaoConfig,
5606 pub ssr: SsrConfig,
5607 pub taa: TaaConfig,
5608 pub dof: DepthOfFieldConfig,
5609 pub volumetric_fog: VolumetricFogConfig,
5610 pub lens_flare: LensFlareConfig,
5611 pub enabled: bool,
5612}
5613
5614impl FullPostProcessConfig {
5615 pub fn new() -> Self {
5616 Self { bloom: BloomConfig::new(), color_grading: ColorGradingSettings::new(), ssao: SsaoConfig::new(), ssr: SsrConfig::new(), taa: TaaConfig::new(), dof: DepthOfFieldConfig::new(), volumetric_fog: VolumetricFogConfig::new(), lens_flare: LensFlareConfig::new(), enabled: true }
5617 }
5618 pub fn high_quality() -> Self {
5619 Self { bloom: BloomConfig::new(), ssao: SsaoConfig::high(), taa: TaaConfig::new(), ..Self::new() }
5620 }
5621 pub fn low_quality() -> Self {
5622 Self { bloom: BloomConfig::disabled(), ssao: SsaoConfig::low(), taa: TaaConfig::new(), ..Self::new() }
5623 }
5624 pub fn active_effect_count(&self) -> usize {
5625 [self.bloom.enabled, self.ssao.enabled, self.ssr.enabled, self.taa.enabled, self.dof.enabled, self.volumetric_fog.enabled, self.lens_flare.enabled].iter().filter(|&&b| b).count()
5626 }
5627}
5628
5629impl Default for FullPostProcessConfig {
5630 fn default() -> Self { Self::new() }
5631}
5632
5633#[derive(Clone, Debug)]
5636pub struct RenderQualityPreset {
5637 pub name: String,
5638 pub features: RenderFeatureFlags,
5639 pub post_process: FullPostProcessConfig,
5640 pub shadow_map_resolution: u32,
5641 pub shadow_cascades: u32,
5642 pub max_lights: u32,
5643 pub reflection_quality: u32,
5644 pub target_fps: u32,
5645 pub render_scale: f32,
5646}
5647
5648impl RenderQualityPreset {
5649 pub fn low() -> Self {
5650 Self { name: "Low".into(), features: RenderFeatureFlags::default_low_quality(), post_process: FullPostProcessConfig::low_quality(), shadow_map_resolution: 1024, shadow_cascades: 2, max_lights: 16, reflection_quality: 0, target_fps: 60, render_scale: 0.75 }
5651 }
5652 pub fn medium() -> Self {
5653 Self { name: "Medium".into(), features: RenderFeatureFlags::default_medium_quality(), post_process: FullPostProcessConfig::new(), shadow_map_resolution: 2048, shadow_cascades: 3, max_lights: 64, reflection_quality: 1, target_fps: 60, render_scale: 1.0 }
5654 }
5655 pub fn high() -> Self {
5656 Self { name: "High".into(), features: RenderFeatureFlags::default_high_quality(), post_process: FullPostProcessConfig::high_quality(), shadow_map_resolution: 4096, shadow_cascades: 4, max_lights: 256, reflection_quality: 2, target_fps: 60, render_scale: 1.0 }
5657 }
5658 pub fn ultra() -> Self {
5659 let mut h = Self::high(); h.name = "Ultra".into(); h.shadow_map_resolution = 8192; h.render_scale = 1.25; h.max_lights = 1024; h
5660 }
5661 pub fn is_mobile_friendly(&self) -> bool { self.shadow_map_resolution <= 1024 && !self.features.ssao && !self.features.ssr }
5662}
5663
5664pub fn build_quality_presets() -> Vec<RenderQualityPreset> {
5667 vec![RenderQualityPreset::low(), RenderQualityPreset::medium(), RenderQualityPreset::high(), RenderQualityPreset::ultra()]
5668}
5669
5670pub const SSR_MAX_STEPS: u32 = 128;
5673pub const TAA_HISTORY_FRAMES: u32 = 8;
5674pub const VOLUMETRIC_FOG_MAX_SLICES: u32 = 256;
5675pub const LENS_FLARE_MAX_GHOSTS: u32 = 16;
5676pub const POST_PROCESS_FULL_EFFECT_COUNT: usize = 8;
5677pub const RENDER_QUALITY_PRESET_COUNT: usize = 4;
5678pub const SHADOW_MAP_MAX_RESOLUTION: u32 = 16384;
5679pub const SHADOW_CASCADE_MAX: u32 = 8;
5680pub const RENDER_MAX_LIGHTS: u32 = 4096;
5681pub const RENDER_SCALE_MIN: f32 = 0.25;
5682pub const RENDER_SCALE_MAX: f32 = 2.0;
5683
5684pub fn render_quality_preset_names() -> &'static [&'static str] { &["Low", "Medium", "High", "Ultra"] }
5685pub fn supported_render_apis() -> &'static [&'static str] { &["OpenGL 4.5", "Vulkan 1.3", "Metal 2", "WebGPU", "D3D12"] }
5686pub fn shader_compiler_total_feature_count() -> usize { shader_module_count() + RENDER_FEATURE_COUNT + POST_PROCESS_FULL_EFFECT_COUNT + RENDER_QUALITY_PRESET_COUNT }
5687pub fn shader_compiler_final_info() -> String {
5688 format!("ShaderCompilerEditor: {} total features, {} render API targets, {} quality presets", shader_compiler_total_feature_count(), supported_render_apis().len(), RENDER_QUALITY_PRESET_COUNT)
5689}
5690
5691
5692#[derive(Clone, Debug)]
5695pub struct InstanceData {
5696 pub model_matrix: [[f32; 4]; 4],
5697 pub color: [f32; 4],
5698 pub custom0: [f32; 4],
5699 pub custom1: [f32; 4],
5700}
5701
5702impl InstanceData {
5703 pub fn new() -> Self { Self { model_matrix: [[1.0,0.0,0.0,0.0],[0.0,1.0,0.0,0.0],[0.0,0.0,1.0,0.0],[0.0,0.0,0.0,1.0]], color: [1.0; 4], custom0: [0.0; 4], custom1: [0.0; 4] } }
5704 pub fn with_color(mut self, r: f32, g: f32, b: f32, a: f32) -> Self { self.color = [r, g, b, a]; self }
5705}
5706
5707impl Default for InstanceData {
5708 fn default() -> Self { Self::new() }
5709}
5710
5711#[derive(Clone, Debug)]
5712pub struct InstanceBatch {
5713 pub id: u32,
5714 pub mesh_id: u32,
5715 pub material_id: u32,
5716 pub instances: Vec<InstanceData>,
5717 pub max_instances: u32,
5718 pub visible_count: u32,
5719 pub cull_enabled: bool,
5720}
5721
5722impl InstanceBatch {
5723 pub fn new(id: u32, mesh_id: u32, material_id: u32, max: u32) -> Self {
5724 Self { id, mesh_id, material_id, instances: Vec::new(), max_instances: max, visible_count: 0, cull_enabled: true }
5725 }
5726 pub fn add(&mut self, data: InstanceData) -> bool {
5727 if self.instances.len() >= self.max_instances as usize { return false; }
5728 self.instances.push(data); self.visible_count += 1; true
5729 }
5730 pub fn clear(&mut self) { self.instances.clear(); self.visible_count = 0; }
5731 pub fn count(&self) -> usize { self.instances.len() }
5732 pub fn is_full(&self) -> bool { self.instances.len() >= self.max_instances as usize }
5733 pub fn utilization(&self) -> f32 { if self.max_instances == 0 { 0.0 } else { self.instances.len() as f32 / self.max_instances as f32 } }
5734}
5735
5736#[derive(Clone, Debug)]
5737pub struct InstanceBatchManager {
5738 pub batches: HashMap<u32, InstanceBatch>,
5739 pub next_id: u32,
5740 pub total_instances: u32,
5741}
5742
5743impl InstanceBatchManager {
5744 pub fn new() -> Self { Self { batches: HashMap::new(), next_id: 1, total_instances: 0 } }
5745 pub fn create_batch(&mut self, mesh_id: u32, material_id: u32, max: u32) -> u32 {
5746 let id = self.next_id; self.next_id += 1;
5747 self.batches.insert(id, InstanceBatch::new(id, mesh_id, material_id, max));
5748 id
5749 }
5750 pub fn add_instance(&mut self, batch_id: u32, data: InstanceData) -> bool {
5751 if let Some(b) = self.batches.get_mut(&batch_id) { if b.add(data) { self.total_instances += 1; return true; } }
5752 false
5753 }
5754 pub fn clear_batch(&mut self, batch_id: u32) {
5755 if let Some(b) = self.batches.get_mut(&batch_id) { self.total_instances -= b.count() as u32; b.clear(); }
5756 }
5757 pub fn clear_all(&mut self) { for b in self.batches.values_mut() { b.clear(); } self.total_instances = 0; }
5758 pub fn batch_count(&self) -> usize { self.batches.len() }
5759 pub fn total_draw_calls(&self) -> usize { self.batches.values().filter(|b| b.count() > 0).count() }
5760}
5761
5762impl Default for InstanceBatchManager {
5763 fn default() -> Self { Self::new() }
5764}
5765
5766#[derive(Clone, Debug)]
5769pub struct OcclusionQuery {
5770 pub id: u32,
5771 pub object_id: u32,
5772 pub visible: bool,
5773 pub pixel_count: u32,
5774 pub pending: bool,
5775 pub frame_issued: u64,
5776}
5777
5778impl OcclusionQuery {
5779 pub fn new(id: u32, object_id: u32) -> Self { Self { id, object_id, visible: true, pixel_count: 0, pending: false, frame_issued: 0 } }
5780 pub fn issue(&mut self, frame: u64) { self.pending = true; self.frame_issued = frame; }
5781 pub fn resolve(&mut self, pixels: u32) { self.pixel_count = pixels; self.visible = pixels > 0; self.pending = false; }
5782 pub fn is_occluded(&self) -> bool { !self.visible }
5783 pub fn is_stale(&self, current_frame: u64) -> bool { current_frame - self.frame_issued > 2 }
5784}
5785
5786#[derive(Clone, Debug)]
5787pub struct OcclusionCullSystem {
5788 pub queries: HashMap<u32, OcclusionQuery>,
5789 pub next_id: u32,
5790 pub enabled: bool,
5791 pub latency_frames: u32,
5792 pub culled_this_frame: u32,
5793 pub visible_this_frame: u32,
5794 pub frame: u64,
5795}
5796
5797impl OcclusionCullSystem {
5798 pub fn new() -> Self { Self { queries: HashMap::new(), next_id: 1, enabled: true, latency_frames: 1, culled_this_frame: 0, visible_this_frame: 0, frame: 0 } }
5799 pub fn register(&mut self, object_id: u32) -> u32 {
5800 let id = self.next_id; self.next_id += 1;
5801 self.queries.insert(id, OcclusionQuery::new(id, object_id));
5802 id
5803 }
5804 pub fn begin_frame(&mut self) { self.frame += 1; self.culled_this_frame = 0; self.visible_this_frame = 0; }
5805 pub fn is_visible(&self, query_id: u32) -> bool { self.queries.get(&query_id).map(|q| q.visible).unwrap_or(true) }
5806 pub fn resolve(&mut self, query_id: u32, pixels: u32) {
5807 if let Some(q) = self.queries.get_mut(&query_id) {
5808 q.resolve(pixels);
5809 if q.visible { self.visible_this_frame += 1; } else { self.culled_this_frame += 1; }
5810 }
5811 }
5812 pub fn cull_rate(&self) -> f32 {
5813 let total = self.culled_this_frame + self.visible_this_frame;
5814 if total == 0 { 0.0 } else { self.culled_this_frame as f32 / total as f32 }
5815 }
5816 pub fn query_count(&self) -> usize { self.queries.len() }
5817}
5818
5819impl Default for OcclusionCullSystem {
5820 fn default() -> Self { Self::new() }
5821}
5822
5823#[derive(Clone, Debug, Default)]
5826pub struct RenderStateTracker {
5827 pub current_shader: Option<u32>,
5828 pub current_material: Option<u32>,
5829 pub current_vao: Option<u32>,
5830 pub current_fbo: Option<u32>,
5831 pub blend_enabled: bool,
5832 pub depth_test_enabled: bool,
5833 pub cull_enabled: bool,
5834 pub scissor_enabled: bool,
5835 pub state_changes: u32,
5836 pub shader_switches: u32,
5837 pub material_switches: u32,
5838 pub texture_uploads: u32,
5839 pub frame: u64,
5840}
5841
5842impl RenderStateTracker {
5843 pub fn new() -> Self { Self { depth_test_enabled: true, cull_enabled: true, ..Default::default() } }
5844 pub fn begin_frame(&mut self) { self.state_changes = 0; self.shader_switches = 0; self.material_switches = 0; self.texture_uploads = 0; self.frame += 1; }
5845 pub fn bind_shader(&mut self, id: u32) -> bool {
5846 if self.current_shader == Some(id) { return false; }
5847 self.current_shader = Some(id); self.shader_switches += 1; self.state_changes += 1; true
5848 }
5849 pub fn bind_material(&mut self, id: u32) -> bool {
5850 if self.current_material == Some(id) { return false; }
5851 self.current_material = Some(id); self.material_switches += 1; self.state_changes += 1; true
5852 }
5853 pub fn redundancy_rate(&self) -> f32 { 0.0 }
5854 pub fn reset_bindings(&mut self) { self.current_shader = None; self.current_material = None; self.current_vao = None; self.current_fbo = None; }
5855}
5856
5857#[derive(Clone, Debug)]
5860pub struct ShadowMapAtlasEntry {
5861 pub light_id: u32,
5862 pub x: u32,
5863 pub y: u32,
5864 pub size: u32,
5865 pub valid: bool,
5866 pub last_updated_frame: u64,
5867}
5868
5869#[derive(Clone, Debug)]
5870pub struct ShadowMapAtlas {
5871 pub atlas_size: u32,
5872 pub entries: Vec<ShadowMapAtlasEntry>,
5873 pub next_x: u32,
5874 pub next_y: u32,
5875 pub current_row_height: u32,
5876}
5877
5878impl ShadowMapAtlas {
5879 pub fn new(atlas_size: u32) -> Self { Self { atlas_size, entries: Vec::new(), next_x: 0, next_y: 0, current_row_height: 0 } }
5880 pub fn allocate(&mut self, light_id: u32, size: u32) -> Option<(u32, u32)> {
5881 if self.next_x + size > self.atlas_size { self.next_x = 0; self.next_y += self.current_row_height; self.current_row_height = 0; }
5882 if self.next_y + size > self.atlas_size { return None; }
5883 let x = self.next_x; let y = self.next_y;
5884 self.current_row_height = self.current_row_height.max(size);
5885 self.next_x += size;
5886 self.entries.push(ShadowMapAtlasEntry { light_id, x, y, size, valid: true, last_updated_frame: 0 });
5887 Some((x, y))
5888 }
5889 pub fn find(&self, light_id: u32) -> Option<&ShadowMapAtlasEntry> { self.entries.iter().find(|e| e.light_id == light_id) }
5890 pub fn entry_count(&self) -> usize { self.entries.len() }
5891 pub fn reset(&mut self) { self.entries.clear(); self.next_x = 0; self.next_y = 0; self.current_row_height = 0; }
5892 pub fn utilization(&self) -> f32 { if self.atlas_size == 0 { 0.0 } else { self.next_y as f32 / self.atlas_size as f32 } }
5893}
5894
5895pub const INSTANCE_BATCH_DEFAULT_MAX: u32 = 1024;
5898pub const OCCLUSION_QUERY_MAX: usize = 4096;
5899pub const SHADOW_ATLAS_DEFAULT_SIZE: u32 = 4096;
5900pub const RENDER_STATE_TRACKER_FRAME_BUDGET: u32 = 1000;
5901pub const INSTANCE_DATA_SIZE_BYTES: usize = 128;
5902
5903pub fn glsl_mat4_from_rows(rows: [[f32; 4]; 4]) -> String {
5904 format!("mat4({:.4},{:.4},{:.4},{:.4}, {:.4},{:.4},{:.4},{:.4}, {:.4},{:.4},{:.4},{:.4}, {:.4},{:.4},{:.4},{:.4})",
5905 rows[0][0], rows[0][1], rows[0][2], rows[0][3],
5906 rows[1][0], rows[1][1], rows[1][2], rows[1][3],
5907 rows[2][0], rows[2][1], rows[2][2], rows[2][3],
5908 rows[3][0], rows[3][1], rows[3][2], rows[3][3])
5909}
5910pub fn glsl_vec4(r: f32, g: f32, b: f32, a: f32) -> String { format!("vec4({:.4}, {:.4}, {:.4}, {:.4})", r, g, b, a) }
5911pub fn glsl_vec3(x: f32, y: f32, z: f32) -> String { format!("vec3({:.4}, {:.4}, {:.4})", x, y, z) }
5912pub fn glsl_vec2(x: f32, y: f32) -> String { format!("vec2({:.4}, {:.4})", x, y) }
5913pub fn glsl_float(v: f32) -> String { format!("{:.6}", v) }
5914pub fn glsl_int(v: i32) -> String { format!("{}", v) }
5915pub fn glsl_bool(v: bool) -> &'static str { if v { "true" } else { "false" } }
5916pub fn glsl_define(name: &str, value: &str) -> String { format!("#define {} {}", name, value) }
5917pub fn glsl_ifdef_block(name: &str, code: &str) -> String { format!("#ifdef {}\n{}\n#endif // {}", name, code, name) }
5918pub fn glsl_version_string(ver: u32, es: bool) -> String { if es { format!("#version {} es", ver) } else { format!("#version {}", ver) } }
5919pub fn glsl_precision_header() -> &'static str { "precision highp float;\nprecision highp int;\nprecision highp sampler2D;\n" }
5920
5921pub fn shader_compiler_all_constants() -> HashMap<&'static str, u64> {
5922 let mut m = HashMap::new();
5923 m.insert("SHADER_MAX_UNIFORMS", SHADER_MAX_UNIFORMS as u64);
5924 m.insert("SHADER_MAX_ATTRIBUTES", SHADER_MAX_ATTRIBUTES as u64);
5925 m.insert("SHADER_MAX_PROGRAMS", SHADER_MAX_PROGRAMS as u64);
5926 m.insert("TEXTURE_MAX_SIZE", TEXTURE_MAX_SIZE as u64);
5927 m.insert("BUFFER_ALIGNMENT", BUFFER_ALIGNMENT as u64);
5928 m.insert("SHADOW_ATLAS_DEFAULT_SIZE", SHADOW_ATLAS_DEFAULT_SIZE as u64);
5929 m.insert("INSTANCE_BATCH_DEFAULT_MAX", INSTANCE_BATCH_DEFAULT_MAX as u64);
5930 m.insert("OCCLUSION_QUERY_MAX", OCCLUSION_QUERY_MAX as u64);
5931 m.insert("BLOOM_MAX_DOWNSAMPLE_PASSES", BLOOM_MAX_DOWNSAMPLE_PASSES as u64);
5932 m.insert("SSAO_KERNEL_SIZE_MAX", SSAO_KERNEL_SIZE_MAX as u64);
5933 m
5934}
5935
5936
5937#[derive(Clone, Debug)]
5940pub struct MotionBlurConfig {
5941 pub enabled: bool,
5942 pub shutter_angle: f32,
5943 pub sample_count: u32,
5944 pub tile_size: u32,
5945 pub max_blur_pixels: f32,
5946 pub motion_scale: f32,
5947 pub camera_motion_blur: bool,
5948 pub object_motion_blur: bool,
5949}
5950
5951impl MotionBlurConfig {
5952 pub fn new() -> Self { Self { enabled: false, shutter_angle: 180.0, sample_count: 8, tile_size: 10, max_blur_pixels: 40.0, motion_scale: 1.0, camera_motion_blur: true, object_motion_blur: true } }
5953 pub fn cinematic() -> Self { Self { enabled: true, shutter_angle: 270.0, sample_count: 16, ..Self::new() } }
5954 pub fn subtle() -> Self { Self { enabled: true, shutter_angle: 90.0, sample_count: 4, max_blur_pixels: 20.0, ..Self::new() } }
5955}
5956
5957impl Default for MotionBlurConfig { fn default() -> Self { Self::new() } }
5958
5959#[derive(Clone, Debug)]
5962pub struct VignetteConfig {
5963 pub enabled: bool,
5964 pub intensity: f32,
5965 pub smoothness: f32,
5966 pub roundness: f32,
5967 pub color: [f32; 3],
5968 pub center: [f32; 2],
5969 pub mask_texture_id: Option<u32>,
5970}
5971
5972impl VignetteConfig {
5973 pub fn new() -> Self { Self { enabled: true, intensity: 0.45, smoothness: 0.2, roundness: 1.0, color: [0.0; 3], center: [0.5, 0.5], mask_texture_id: None } }
5974 pub fn subtle() -> Self { Self { intensity: 0.2, ..Self::new() } }
5975 pub fn dramatic() -> Self { Self { intensity: 0.8, smoothness: 0.4, ..Self::new() } }
5976 pub fn colored(r: f32, g: f32, b: f32) -> Self { Self { color: [r, g, b], ..Self::new() } }
5977 pub fn disabled() -> Self { Self { enabled: false, ..Self::new() } }
5978}
5979
5980impl Default for VignetteConfig { fn default() -> Self { Self::new() } }
5981
5982#[derive(Clone, Debug)]
5985pub struct ChromaticAberrationConfig {
5986 pub enabled: bool,
5987 pub intensity: f32,
5988 pub radial: bool,
5989 pub offset_r: [f32; 2],
5990 pub offset_g: [f32; 2],
5991 pub offset_b: [f32; 2],
5992 pub sample_count: u32,
5993}
5994
5995impl ChromaticAberrationConfig {
5996 pub fn new() -> Self { Self { enabled: false, intensity: 0.5, radial: true, offset_r: [-0.01, 0.0], offset_g: [0.0, 0.0], offset_b: [0.01, 0.0], sample_count: 3 } }
5997 pub fn subtle() -> Self { Self { enabled: true, intensity: 0.2, ..Self::new() } }
5998 pub fn strong() -> Self { Self { enabled: true, intensity: 1.5, sample_count: 5, ..Self::new() } }
5999}
6000
6001impl Default for ChromaticAberrationConfig { fn default() -> Self { Self::new() } }
6002
6003#[derive(Clone, Debug)]
6006pub struct FilmGrainConfig {
6007 pub enabled: bool,
6008 pub intensity: f32,
6009 pub size: f32,
6010 pub luminance_contribution: f32,
6011 pub color: bool,
6012 pub animated: bool,
6013}
6014
6015impl FilmGrainConfig {
6016 pub fn new() -> Self { Self { enabled: false, intensity: 0.1, size: 1.5, luminance_contribution: 0.8, color: false, animated: true } }
6017 pub fn subtle() -> Self { Self { enabled: true, intensity: 0.05, ..Self::new() } }
6018 pub fn heavy() -> Self { Self { enabled: true, intensity: 0.4, ..Self::new() } }
6019}
6020
6021impl Default for FilmGrainConfig { fn default() -> Self { Self::new() } }
6022
6023#[derive(Clone, Debug)]
6026pub struct AntiAliasingConfig {
6027 pub mode: AntiAliasingMode,
6028 pub msaa_samples: u32,
6029 pub fxaa: bool,
6030 pub taa: Option<TaaConfig>,
6031 pub smaa_quality: u32,
6032}
6033
6034#[derive(Clone, Debug, PartialEq)]
6035pub enum AntiAliasingMode { None, Msaa, Fxaa, Taa, Smaa, Dlss }
6036
6037impl AntiAliasingConfig {
6038 pub fn none() -> Self { Self { mode: AntiAliasingMode::None, msaa_samples: 1, fxaa: false, taa: None, smaa_quality: 0 } }
6039 pub fn fxaa() -> Self { Self { mode: AntiAliasingMode::Fxaa, fxaa: true, ..Self::none() } }
6040 pub fn taa() -> Self { Self { mode: AntiAliasingMode::Taa, taa: Some(TaaConfig::new()), ..Self::none() } }
6041 pub fn msaa(samples: u32) -> Self { Self { mode: AntiAliasingMode::Msaa, msaa_samples: samples, ..Self::none() } }
6042 pub fn smaa(quality: u32) -> Self { Self { mode: AntiAliasingMode::Smaa, smaa_quality: quality, ..Self::none() } }
6043 pub fn is_temporal(&self) -> bool { self.mode == AntiAliasingMode::Taa || self.mode == AntiAliasingMode::Dlss }
6044 pub fn requires_velocity_buffer(&self) -> bool { self.is_temporal() }
6045}
6046
6047impl Default for AntiAliasingConfig { fn default() -> Self { Self::fxaa() } }
6048
6049#[derive(Clone, Debug, Default)]
6052pub struct ExtendedPostProcessConfig {
6053 pub motion_blur: MotionBlurConfig,
6054 pub vignette: VignetteConfig,
6055 pub chromatic_aberration: ChromaticAberrationConfig,
6056 pub film_grain: FilmGrainConfig,
6057 pub anti_aliasing: AntiAliasingConfig,
6058}
6059
6060impl ExtendedPostProcessConfig {
6061 pub fn new() -> Self { Self::default() }
6062 pub fn cinematic() -> Self {
6063 Self { motion_blur: MotionBlurConfig::cinematic(), vignette: VignetteConfig::dramatic(), chromatic_aberration: ChromaticAberrationConfig::subtle(), film_grain: FilmGrainConfig::subtle(), anti_aliasing: AntiAliasingConfig::taa() }
6064 }
6065 pub fn game() -> Self {
6066 Self { motion_blur: MotionBlurConfig::subtle(), vignette: VignetteConfig::subtle(), anti_aliasing: AntiAliasingConfig::fxaa(), ..Self::new() }
6067 }
6068 pub fn count_active(&self) -> usize {
6069 [self.motion_blur.enabled, self.vignette.enabled, self.chromatic_aberration.enabled, self.film_grain.enabled].iter().filter(|&&b| b).count()
6070 }
6071}
6072
6073#[derive(Clone, Debug, PartialEq)]
6076pub enum GpuProgramState { Uncompiled, Compiling, Compiled, Failed, Stale }
6077
6078#[derive(Clone, Debug)]
6079pub struct GpuProgramLifecycle {
6080 pub program_id: u32,
6081 pub state: GpuProgramState,
6082 pub compile_attempts: u32,
6083 pub last_success: Option<u64>,
6084 pub last_failure: Option<u64>,
6085 pub auto_retry: bool,
6086 pub max_retries: u32,
6087}
6088
6089impl GpuProgramLifecycle {
6090 pub fn new(program_id: u32) -> Self { Self { program_id, state: GpuProgramState::Uncompiled, compile_attempts: 0, last_success: None, last_failure: None, auto_retry: true, max_retries: 3 } }
6091 pub fn begin_compile(&mut self) { self.state = GpuProgramState::Compiling; self.compile_attempts += 1; }
6092 pub fn mark_success(&mut self, ts: u64) { self.state = GpuProgramState::Compiled; self.last_success = Some(ts); }
6093 pub fn mark_failure(&mut self, ts: u64) { self.state = GpuProgramState::Failed; self.last_failure = Some(ts); }
6094 pub fn mark_stale(&mut self) { self.state = GpuProgramState::Stale; }
6095 pub fn can_retry(&self) -> bool { self.auto_retry && self.compile_attempts < self.max_retries }
6096 pub fn is_compiled(&self) -> bool { self.state == GpuProgramState::Compiled }
6097 pub fn is_failed(&self) -> bool { self.state == GpuProgramState::Failed }
6098}
6099
6100pub const MOTION_BLUR_MAX_SAMPLES: u32 = 32;
6103pub const FILM_GRAIN_MAX_SIZE: f32 = 10.0;
6104pub const VIGNETTE_MAX_INTENSITY: f32 = 1.0;
6105pub const CHROMATIC_MAX_OFFSET: f32 = 0.1;
6106pub const AA_MSAA_MAX_SAMPLES: u32 = 8;
6107pub const SHADER_GPU_PROGRAM_MAX_RETRIES: u32 = 3;
6108pub const RENDER_EXTENDED_PP_EFFECT_COUNT: usize = 5;
6109
6110pub fn anti_aliasing_name(mode: &AntiAliasingMode) -> &'static str {
6111 match mode { AntiAliasingMode::None => "None", AntiAliasingMode::Msaa => "MSAA", AntiAliasingMode::Fxaa => "FXAA", AntiAliasingMode::Taa => "TAA", AntiAliasingMode::Smaa => "SMAA", AntiAliasingMode::Dlss => "DLSS" }
6112}
6113pub fn gpu_program_state_name(s: &GpuProgramState) -> &'static str {
6114 match s { GpuProgramState::Uncompiled => "Uncompiled", GpuProgramState::Compiling => "Compiling", GpuProgramState::Compiled => "Compiled", GpuProgramState::Failed => "Failed", GpuProgramState::Stale => "Stale" }
6115}
6116pub fn shader_editor_complete_version() -> String { format!("ShaderCompilerEditor — complete build, v{}", shader_system_version()) }
6117
6118
6119#[derive(Clone, Debug)]
6122pub struct InputAssemblerConfig {
6123 pub topology: PrimitiveTopology,
6124 pub restart_enabled: bool,
6125 pub restart_index: u32,
6126 pub vertex_layout: VertexLayout,
6127}
6128
6129#[derive(Clone, Debug, PartialEq)]
6130pub enum PrimitiveTopology { PointList, LineList, LineStrip, TriangleList, TriangleStrip, TriangleFan, PatchList(u32) }
6131
6132impl PrimitiveTopology {
6133 pub fn name(&self) -> &'static str {
6134 match self { PrimitiveTopology::PointList => "PointList", PrimitiveTopology::LineList => "LineList", PrimitiveTopology::LineStrip => "LineStrip", PrimitiveTopology::TriangleList => "TriangleList", PrimitiveTopology::TriangleStrip => "TriangleStrip", PrimitiveTopology::TriangleFan => "TriangleFan", PrimitiveTopology::PatchList(_) => "PatchList" }
6135 }
6136 pub fn is_strip(&self) -> bool { matches!(self, PrimitiveTopology::LineStrip | PrimitiveTopology::TriangleStrip) }
6137 pub fn is_triangle(&self) -> bool { matches!(self, PrimitiveTopology::TriangleList | PrimitiveTopology::TriangleStrip | PrimitiveTopology::TriangleFan) }
6138}
6139
6140impl InputAssemblerConfig {
6141 pub fn triangles(layout: VertexLayout) -> Self { Self { topology: PrimitiveTopology::TriangleList, restart_enabled: false, restart_index: 0xFFFFFFFF, vertex_layout: layout } }
6142 pub fn lines(layout: VertexLayout) -> Self { Self { topology: PrimitiveTopology::LineList, restart_enabled: false, restart_index: 0xFFFFFFFF, vertex_layout: layout } }
6143 pub fn points(layout: VertexLayout) -> Self { Self { topology: PrimitiveTopology::PointList, restart_enabled: false, restart_index: 0xFFFFFFFF, vertex_layout: layout } }
6144}
6145
6146#[derive(Clone, Debug)]
6149pub struct StencilConfig {
6150 pub enabled: bool,
6151 pub ref_value: u32,
6152 pub read_mask: u32,
6153 pub write_mask: u32,
6154 pub fail_op: StencilOp,
6155 pub depth_fail_op: StencilOp,
6156 pub pass_op: StencilOp,
6157 pub compare_func: CompareFunc,
6158}
6159
6160#[derive(Clone, Debug, PartialEq)]
6161pub enum StencilOp { Keep, Zero, Replace, IncrClamp, DecrClamp, Invert, IncrWrap, DecrWrap }
6162
6163impl StencilConfig {
6164 pub fn disabled() -> Self { Self { enabled: false, ref_value: 0, read_mask: 0xFF, write_mask: 0xFF, fail_op: StencilOp::Keep, depth_fail_op: StencilOp::Keep, pass_op: StencilOp::Keep, compare_func: CompareFunc::Always } }
6165 pub fn write_mask(mask: u32) -> Self { Self { enabled: true, write_mask: mask, pass_op: StencilOp::Replace, ref_value: mask, ..Self::disabled() } }
6166 pub fn test_mask(mask: u32) -> Self { Self { enabled: true, read_mask: mask, ref_value: mask, ..Self::disabled() } }
6167}
6168
6169impl Default for StencilConfig { fn default() -> Self { Self::disabled() } }
6170
6171#[derive(Clone, Debug)]
6174pub struct ViewportConfig {
6175 pub x: f32,
6176 pub y: f32,
6177 pub width: f32,
6178 pub height: f32,
6179 pub min_depth: f32,
6180 pub max_depth: f32,
6181}
6182
6183impl ViewportConfig {
6184 pub fn new(width: f32, height: f32) -> Self { Self { x: 0.0, y: 0.0, width, height, min_depth: 0.0, max_depth: 1.0 } }
6185 pub fn aspect_ratio(&self) -> f32 { if self.height == 0.0 { 1.0 } else { self.width / self.height } }
6186 pub fn scale(mut self, s: f32) -> Self { self.width *= s; self.height *= s; self }
6187 pub fn ndc_to_pixel(&self, ndc_x: f32, ndc_y: f32) -> (f32, f32) {
6188 ((ndc_x * 0.5 + 0.5) * self.width + self.x, (ndc_y * -0.5 + 0.5) * self.height + self.y)
6189 }
6190 pub fn pixel_to_ndc(&self, px: f32, py: f32) -> (f32, f32) {
6191 ((px - self.x) / self.width * 2.0 - 1.0, -((py - self.y) / self.height * 2.0 - 1.0))
6192 }
6193}
6194
6195impl Default for ViewportConfig { fn default() -> Self { Self::new(1920.0, 1080.0) } }
6196
6197#[derive(Clone, Debug)]
6200pub struct RasterizerState {
6201 pub cull_mode: CullMode,
6202 pub front_face: FrontFace,
6203 pub polygon_mode: PolygonMode,
6204 pub depth_bias: f32,
6205 pub depth_bias_slope_scale: f32,
6206 pub depth_bias_clamp: f32,
6207 pub depth_clamp: bool,
6208 pub rasterizer_discard: bool,
6209 pub multisample: bool,
6210 pub line_width: f32,
6211 pub conservative: bool,
6212}
6213
6214impl Default for RasterizerState {
6215 fn default() -> Self { Self { cull_mode: CullMode::Back, front_face: FrontFace::CounterClockwise, polygon_mode: PolygonMode::Fill, depth_bias: 0.0, depth_bias_slope_scale: 0.0, depth_bias_clamp: 0.0, depth_clamp: false, rasterizer_discard: false, multisample: false, line_width: 1.0, conservative: false } }
6216}
6217
6218impl RasterizerState {
6219 pub fn wireframe() -> Self { Self { polygon_mode: PolygonMode::Line, cull_mode: CullMode::None, ..Default::default() } }
6220 pub fn shadow_map() -> Self { Self { depth_bias: 1.0, depth_bias_slope_scale: 1.5, ..Default::default() } }
6221 pub fn two_sided() -> Self { Self { cull_mode: CullMode::None, ..Default::default() } }
6222 pub fn is_wireframe(&self) -> bool { self.polygon_mode == PolygonMode::Line }
6223 pub fn has_depth_bias(&self) -> bool { self.depth_bias.abs() > 1e-6 || self.depth_bias_slope_scale.abs() > 1e-6 }
6224}
6225
6226#[derive(Clone, Debug)]
6229pub struct GraphicsPipelineDescriptor {
6230 pub name: String,
6231 pub vertex_shader_id: u32,
6232 pub fragment_shader_id: u32,
6233 pub geometry_shader_id: Option<u32>,
6234 pub input_assembly: InputAssemblerConfig,
6235 pub rasterizer: RasterizerState,
6236 pub depth_stencil: (bool, bool, CompareFunc, StencilConfig),
6237 pub blend_state: PipelineState,
6238 pub viewport: ViewportConfig,
6239 pub render_pass_id: u32,
6240 pub subpass: u32,
6241 pub layout: PipelineLayout,
6242}
6243
6244impl GraphicsPipelineDescriptor {
6245 pub fn new(name: impl Into<String>, vert_id: u32, frag_id: u32) -> Self {
6246 Self { name: name.into(), vertex_shader_id: vert_id, fragment_shader_id: frag_id, geometry_shader_id: None, input_assembly: InputAssemblerConfig::triangles(VertexLayout::standard_mesh()), rasterizer: RasterizerState::default(), depth_stencil: (true, true, CompareFunc::Less, StencilConfig::disabled()), blend_state: PipelineState::opaque(), viewport: ViewportConfig::default(), render_pass_id: 0, subpass: 0, layout: PipelineLayout::new() }
6247 }
6248 pub fn transparent(name: impl Into<String>, vert_id: u32, frag_id: u32) -> Self {
6249 let mut d = Self::new(name, vert_id, frag_id); d.blend_state = PipelineState::transparent(); d.depth_stencil.1 = false; d
6250 }
6251 pub fn has_geometry_shader(&self) -> bool { self.geometry_shader_id.is_some() }
6252 pub fn depth_test_enabled(&self) -> bool { self.depth_stencil.0 }
6253 pub fn depth_write_enabled(&self) -> bool { self.depth_stencil.1 }
6254}
6255
6256pub const STENCIL_MAX_REF_VALUE: u32 = 255;
6259pub const VIEWPORT_MAX_RENDER_SCALE: f32 = 2.0;
6260pub const RASTERIZER_MAX_LINE_WIDTH: f32 = 16.0;
6261pub const GRAPHICS_PIPELINE_MAX: usize = 512;
6262pub const INPUT_ASSEMBLER_MAX_VERTEX_STREAMS: usize = 8;
6263
6264pub fn stencil_op_name(op: &StencilOp) -> &'static str {
6265 match op { StencilOp::Keep => "Keep", StencilOp::Zero => "Zero", StencilOp::Replace => "Replace", StencilOp::IncrClamp => "IncrClamp", StencilOp::DecrClamp => "DecrClamp", StencilOp::Invert => "Invert", StencilOp::IncrWrap => "IncrWrap", StencilOp::DecrWrap => "DecrWrap" }
6266}
6267pub fn primitive_topology_vertex_count(topo: &PrimitiveTopology, primitive_count: u32) -> u32 {
6268 match topo { PrimitiveTopology::PointList => primitive_count, PrimitiveTopology::LineList => primitive_count * 2, PrimitiveTopology::LineStrip => primitive_count + 1, PrimitiveTopology::TriangleList => primitive_count * 3, PrimitiveTopology::TriangleStrip | PrimitiveTopology::TriangleFan => primitive_count + 2, PrimitiveTopology::PatchList(n) => primitive_count * n }
6269}
6270
6271pub fn shader_compiler_feature_summary() -> String {
6272 let features = shader_compiler_module_list();
6273 format!("ShaderCompilerEditor v{}: {} modules — {}", shader_system_version(), features.len(), features.join(", "))
6274}
6275
6276
6277#[derive(Clone, Debug)]
6280pub struct RenderTargetSet {
6281 pub id: u32,
6282 pub name: String,
6283 pub width: u32,
6284 pub height: u32,
6285 pub color_targets: Vec<u32>,
6286 pub depth_target: Option<u32>,
6287 pub msaa_samples: u32,
6288 pub active: bool,
6289}
6290
6291impl RenderTargetSet {
6292 pub fn new(id: u32, name: impl Into<String>, w: u32, h: u32) -> Self {
6293 Self { id, name: name.into(), width: w, height: h, color_targets: Vec::new(), depth_target: None, msaa_samples: 1, active: false }
6294 }
6295 pub fn add_color(&mut self, tex_id: u32) { self.color_targets.push(tex_id); }
6296 pub fn set_depth(&mut self, tex_id: u32) { self.depth_target = Some(tex_id); }
6297 pub fn enable(&mut self) { self.active = true; }
6298 pub fn disable(&mut self) { self.active = false; }
6299 pub fn color_count(&self) -> usize { self.color_targets.len() }
6300 pub fn has_depth(&self) -> bool { self.depth_target.is_some() }
6301 pub fn total_attachments(&self) -> usize { self.color_targets.len() + if self.has_depth() { 1 } else { 0 } }
6302 pub fn aspect_ratio(&self) -> f32 { if self.height == 0 { 1.0 } else { self.width as f32 / self.height as f32 } }
6303 pub fn is_multisampled(&self) -> bool { self.msaa_samples > 1 }
6304 pub fn total_bytes(&self, bpp: u32) -> u64 { self.width as u64 * self.height as u64 * bpp as u64 * (self.color_targets.len() + 1) as u64 }
6305}
6306
6307#[derive(Clone, Debug)]
6308pub struct RenderTargetManager {
6309 pub sets: HashMap<u32, RenderTargetSet>,
6310 pub active_set: Option<u32>,
6311 pub next_id: u32,
6312 pub backbuffer_id: u32,
6313}
6314
6315impl RenderTargetManager {
6316 pub fn new() -> Self { Self { sets: HashMap::new(), active_set: None, next_id: 1, backbuffer_id: 0 } }
6317 pub fn create(&mut self, name: impl Into<String>, w: u32, h: u32) -> u32 {
6318 let id = self.next_id; self.next_id += 1;
6319 self.sets.insert(id, RenderTargetSet::new(id, name, w, h));
6320 id
6321 }
6322 pub fn get(&self, id: u32) -> Option<&RenderTargetSet> { self.sets.get(&id) }
6323 pub fn bind(&mut self, id: u32) { if let Some(s) = self.sets.get_mut(&id) { s.enable(); } self.active_set = Some(id); }
6324 pub fn unbind(&mut self) { if let Some(id) = self.active_set { if let Some(s) = self.sets.get_mut(&id) { s.disable(); } } self.active_set = None; }
6325 pub fn resize_all(&mut self, w: u32, h: u32) { for s in self.sets.values_mut() { s.width = w; s.height = h; } }
6326 pub fn count(&self) -> usize { self.sets.len() }
6327 pub fn active(&self) -> Option<&RenderTargetSet> { self.active_set.and_then(|id| self.sets.get(&id)) }
6328}
6329
6330impl Default for RenderTargetManager { fn default() -> Self { Self::new() } }
6331
6332#[derive(Clone, Debug)]
6335pub struct FrameGraphExecutor {
6336 pub render_graph: RenderGraph,
6337 pub rt_manager: RenderTargetManager,
6338 pub frame: u64,
6339 pub paused: bool,
6340}
6341
6342impl FrameGraphExecutor {
6343 pub fn new() -> Self { Self { render_graph: RenderGraph::new("frame"), rt_manager: RenderTargetManager::new(), frame: 0, paused: false } }
6344 pub fn begin_frame(&mut self) { if !self.paused { self.frame += 1; } }
6345 pub fn pause(&mut self) { self.paused = true; }
6346 pub fn resume(&mut self) { self.paused = false; }
6347 pub fn node_count(&self) -> usize { self.render_graph.node_count() }
6348 pub fn is_running(&self) -> bool { !self.paused }
6349 pub fn frame_number(&self) -> u64 { self.frame }
6350}
6351
6352impl Default for FrameGraphExecutor { fn default() -> Self { Self::new() } }
6353
6354#[derive(Clone, Debug, Default)]
6357pub struct ShaderCompilerStats {
6358 pub total_programs: usize,
6359 pub total_materials: usize,
6360 pub total_textures: usize,
6361 pub total_buffers: usize,
6362 pub total_passes: usize,
6363 pub total_post_effects: usize,
6364 pub cache_entries: usize,
6365 pub cache_hit_rate: f32,
6366 pub total_compile_ms: f32,
6367 pub failed_compiles: u32,
6368 pub hot_reloads: u32,
6369}
6370
6371impl ShaderCompilerStats {
6372 pub fn from_workspace(ws: &ShaderWorkspace) -> Self {
6373 Self {
6374 total_programs: ws.program_count(),
6375 total_materials: ws.material_count(),
6376 total_textures: ws.texture_count(),
6377 total_buffers: ws.buffer_count(),
6378 total_passes: ws.pipeline.pass_count(),
6379 total_post_effects: ws.post_process.effect_count(),
6380 cache_entries: ws.cache.size(),
6381 cache_hit_rate: ws.cache.hit_rate(),
6382 ..Default::default()
6383 }
6384 }
6385 pub fn summary(&self) -> String {
6386 format!("Programs:{} Materials:{} Textures:{} Buffers:{} Cache:{:.0}%", self.total_programs, self.total_materials, self.total_textures, self.total_buffers, self.cache_hit_rate * 100.0)
6387 }
6388}
6389
6390pub const RENDER_TARGET_MAX: usize = 64;
6393pub const FRAME_GRAPH_MAX_NODES: usize = 128;
6394pub const RT_MAX_COLOR_ATTACHMENTS: usize = 8;
6395pub const RT_DEFAULT_MSAA: u32 = 1;
6396pub const SHADER_COMPILER_SESSION_MAX: usize = 4;
6397
6398pub fn all_post_process_effect_names() -> &'static [&'static str] {
6399 &["Bloom", "Tone Mapping", "SSAO", "SSR", "TAA", "Depth of Field", "Volumetric Fog", "Lens Flare", "Motion Blur", "Vignette", "Chromatic Aberration", "Film Grain", "FXAA", "Color Grading", "SMAA"]
6400}
6401
6402pub fn shader_compiler_comprehensive_info() -> String {
6403 format!(concat!(
6404 "ShaderCompilerEditor v{} Summary:\n",
6405 " Modules: {}\n",
6406 " Render API targets: {}\n",
6407 " Quality presets: {}\n",
6408 " Post-process effects: {}\n",
6409 " Builtin GLSL snippets: {}\n",
6410 " Builtin GLSL functions: {}\n"
6411 ),
6412 shader_system_version(),
6413 shader_module_count(),
6414 supported_render_apis().len(),
6415 RENDER_QUALITY_PRESET_COUNT,
6416 all_post_process_effect_names().len(),
6417 SHADER_BUILTIN_SNIPPETS,
6418 SHADER_GLSL_BUILTIN_FUNCTIONS)
6419}
6420
6421
6422#[derive(Clone, Debug)]
6425pub struct ShaderManager {
6426 pub compiler: ShaderCompilerMain,
6427 pub post_config: FullPostProcessConfig,
6428 pub extended_post: ExtendedPostProcessConfig,
6429 pub anti_aliasing: AntiAliasingConfig,
6430 pub quality_preset: String,
6431 pub quality_presets: Vec<RenderQualityPreset>,
6432 pub frame_executor: FrameGraphExecutor,
6433 pub rt_manager: RenderTargetManager,
6434 pub uniform_tracker: UniformTracker,
6435 pub occlusion_cull: OcclusionCullSystem,
6436 pub instance_batches: InstanceBatchManager,
6437 pub render_state: RenderStateTracker,
6438 pub error_reports: Vec<ShaderErrorReport>,
6439 pub compile_stats: CompileStats,
6440}
6441
6442impl ShaderManager {
6443 pub fn new() -> Self {
6444 Self {
6445 compiler: ShaderCompilerMain::new(),
6446 post_config: FullPostProcessConfig::new(),
6447 extended_post: ExtendedPostProcessConfig::new(),
6448 anti_aliasing: AntiAliasingConfig::default(),
6449 quality_preset: "High".into(),
6450 quality_presets: build_quality_presets(),
6451 frame_executor: FrameGraphExecutor::new(),
6452 rt_manager: RenderTargetManager::new(),
6453 uniform_tracker: UniformTracker::new(),
6454 occlusion_cull: OcclusionCullSystem::new(),
6455 instance_batches: InstanceBatchManager::new(),
6456 render_state: RenderStateTracker::new(),
6457 error_reports: Vec::new(),
6458 compile_stats: CompileStats::new(),
6459 }
6460 }
6461 pub fn add_program(&mut self, prog: ShaderProgram) -> u32 { self.compiler.add_program(prog) }
6462 pub fn add_material(&mut self, mat: Material) -> u32 { self.compiler.add_material(mat) }
6463 pub fn begin_frame(&mut self) { self.compiler.begin_frame(); self.frame_executor.begin_frame(); self.render_state.begin_frame(); self.occlusion_cull.begin_frame(); self.instance_batches.clear_all(); }
6464 pub fn set_quality(&mut self, preset_name: &str) { if self.quality_presets.iter().any(|p| p.name == preset_name) { self.quality_preset = preset_name.to_string(); } }
6465 pub fn record_error(&mut self, report: ShaderErrorReport) { self.error_reports.push(report); }
6466 pub fn clear_errors(&mut self) { self.error_reports.clear(); }
6467 pub fn has_errors(&self) -> bool { !self.error_reports.is_empty() }
6468 pub fn error_count(&self) -> usize { self.error_reports.len() }
6469 pub fn program_count(&self) -> usize { self.compiler.program_count() }
6470 pub fn compilation_summary(&self) -> String { self.compiler.compilation_summary() }
6471 pub fn stats(&self) -> ShaderCompilerStats { ShaderCompilerStats::from_workspace(&self.compiler.workspace) }
6472}
6473
6474impl Default for ShaderManager { fn default() -> Self { Self::new() } }
6475
6476pub struct GlslIntrinsicDoc {
6479 pub name: &'static str,
6480 pub signature: &'static str,
6481 pub description: &'static str,
6482 pub category: &'static str,
6483}
6484
6485pub const GLSL_DOCS: &[GlslIntrinsicDoc] = &[
6486 GlslIntrinsicDoc { name: "radians", signature: "genType radians(genType degrees)", description: "Convert degrees to radians", category: "trigonometry" },
6487 GlslIntrinsicDoc { name: "degrees", signature: "genType degrees(genType radians)", description: "Convert radians to degrees", category: "trigonometry" },
6488 GlslIntrinsicDoc { name: "sin", signature: "genType sin(genType angle)", description: "Sine of angle in radians", category: "trigonometry" },
6489 GlslIntrinsicDoc { name: "cos", signature: "genType cos(genType angle)", description: "Cosine of angle in radians", category: "trigonometry" },
6490 GlslIntrinsicDoc { name: "tan", signature: "genType tan(genType angle)", description: "Tangent of angle in radians", category: "trigonometry" },
6491 GlslIntrinsicDoc { name: "asin", signature: "genType asin(genType x)", description: "Arc sine", category: "trigonometry" },
6492 GlslIntrinsicDoc { name: "acos", signature: "genType acos(genType x)", description: "Arc cosine", category: "trigonometry" },
6493 GlslIntrinsicDoc { name: "atan", signature: "genType atan(genType y_over_x)", description: "Arc tangent", category: "trigonometry" },
6494 GlslIntrinsicDoc { name: "sinh", signature: "genType sinh(genType x)", description: "Hyperbolic sine", category: "trigonometry" },
6495 GlslIntrinsicDoc { name: "cosh", signature: "genType cosh(genType x)", description: "Hyperbolic cosine", category: "trigonometry" },
6496 GlslIntrinsicDoc { name: "pow", signature: "genType pow(genType x, genType y)", description: "x raised to y power", category: "exponential" },
6497 GlslIntrinsicDoc { name: "exp", signature: "genType exp(genType x)", description: "e raised to x", category: "exponential" },
6498 GlslIntrinsicDoc { name: "log", signature: "genType log(genType x)", description: "Natural logarithm", category: "exponential" },
6499 GlslIntrinsicDoc { name: "exp2", signature: "genType exp2(genType x)", description: "2 raised to x", category: "exponential" },
6500 GlslIntrinsicDoc { name: "log2", signature: "genType log2(genType x)", description: "Base-2 logarithm", category: "exponential" },
6501 GlslIntrinsicDoc { name: "sqrt", signature: "genType sqrt(genType x)", description: "Square root", category: "exponential" },
6502 GlslIntrinsicDoc { name: "inversesqrt", signature: "genType inversesqrt(genType x)", description: "Inverse square root", category: "exponential" },
6503 GlslIntrinsicDoc { name: "abs", signature: "genType abs(genType x)", description: "Absolute value", category: "common" },
6504 GlslIntrinsicDoc { name: "sign", signature: "genType sign(genType x)", description: "Sign of x", category: "common" },
6505 GlslIntrinsicDoc { name: "floor", signature: "genType floor(genType x)", description: "Floor function", category: "common" },
6506 GlslIntrinsicDoc { name: "ceil", signature: "genType ceil(genType x)", description: "Ceiling function", category: "common" },
6507 GlslIntrinsicDoc { name: "round", signature: "genType round(genType x)", description: "Round to nearest", category: "common" },
6508 GlslIntrinsicDoc { name: "fract", signature: "genType fract(genType x)", description: "Fractional part", category: "common" },
6509 GlslIntrinsicDoc { name: "mod", signature: "genType mod(genType x, genType y)", description: "Modulo operation", category: "common" },
6510 GlslIntrinsicDoc { name: "min", signature: "genType min(genType x, genType y)", description: "Minimum of two values", category: "common" },
6511 GlslIntrinsicDoc { name: "max", signature: "genType max(genType x, genType y)", description: "Maximum of two values", category: "common" },
6512 GlslIntrinsicDoc { name: "clamp", signature: "genType clamp(genType x, genType minVal, genType maxVal)", description: "Clamp x between min and max", category: "common" },
6513 GlslIntrinsicDoc { name: "mix", signature: "genType mix(genType x, genType y, genType a)", description: "Linear interpolation", category: "common" },
6514 GlslIntrinsicDoc { name: "step", signature: "genType step(genType edge, genType x)", description: "Step function", category: "common" },
6515 GlslIntrinsicDoc { name: "smoothstep", signature: "genType smoothstep(genType edge0, genType edge1, genType x)", description: "Smooth step function", category: "common" },
6516];
6517
6518pub fn find_glsl_doc(name: &str) -> Option<&'static GlslIntrinsicDoc> { GLSL_DOCS.iter().find(|d| d.name == name) }
6519pub fn glsl_doc_count() -> usize { GLSL_DOCS.len() }
6520pub fn glsl_docs_in_category(cat: &str) -> Vec<&'static GlslIntrinsicDoc> { GLSL_DOCS.iter().filter(|d| d.category == cat).collect() }
6521
6522pub const SHADER_MANAGER_VERSION: &str = "ShaderManager v1.0";
6523pub const SHADER_GLSL_INTRINSIC_DOC_COUNT: usize = 30;
6524pub const SHADER_MAX_ERROR_REPORTS: usize = 256;
6525pub const FRAME_GRAPH_EXECUTOR_MAX_NODES: usize = 128;
6526
6527pub fn shader_manager_info() -> String {
6528 format!("{} — {} programs max, {} intrinsics documented, {} quality presets", SHADER_MANAGER_VERSION, SHADER_MAX_PROGRAMS, SHADER_GLSL_INTRINSIC_DOC_COUNT, RENDER_QUALITY_PRESET_COUNT)
6529}
6530
6531
6532#[derive(Clone, Debug)]
6535pub struct GeometryShaderConfig {
6536 pub input_primitive: GeometryPrimitive,
6537 pub output_primitive: GeometryPrimitive,
6538 pub max_vertices_out: u32,
6539 pub invocations: u32,
6540}
6541
6542#[derive(Clone, Debug, PartialEq)]
6543pub enum GeometryPrimitive { Points, Lines, LinesAdjacency, Triangles, TrianglesAdjacency, LineStrip, TriangleStrip }
6544
6545impl GeometryShaderConfig {
6546 pub fn passthrough_triangles() -> Self { Self { input_primitive: GeometryPrimitive::Triangles, output_primitive: GeometryPrimitive::TriangleStrip, max_vertices_out: 3, invocations: 1 } }
6547 pub fn silhouette_detection() -> Self { Self { input_primitive: GeometryPrimitive::TrianglesAdjacency, output_primitive: GeometryPrimitive::LineStrip, max_vertices_out: 4, invocations: 1 } }
6548 pub fn shadow_volumes() -> Self { Self { input_primitive: GeometryPrimitive::Triangles, output_primitive: GeometryPrimitive::TriangleStrip, max_vertices_out: 18, invocations: 1 } }
6549 pub fn point_sprites() -> Self { Self { input_primitive: GeometryPrimitive::Points, output_primitive: GeometryPrimitive::TriangleStrip, max_vertices_out: 4, invocations: 1 } }
6550 pub fn cubemap_rendering() -> Self { Self { input_primitive: GeometryPrimitive::Triangles, output_primitive: GeometryPrimitive::TriangleStrip, max_vertices_out: 18, invocations: 6 } }
6551}
6552
6553impl Default for GeometryShaderConfig { fn default() -> Self { Self::passthrough_triangles() } }
6554
6555#[derive(Clone, Debug)]
6558pub struct TessellationConfig {
6559 pub enabled: bool,
6560 pub patch_size: u32,
6561 pub outer_level: [f32; 4],
6562 pub inner_level: [f32; 2],
6563 pub spacing: TessSpacing,
6564 pub winding: TessWinding,
6565 pub primitive: TessPrimitive,
6566 pub point_mode: bool,
6567 pub adaptive: bool,
6568 pub max_tess_level: f32,
6569 pub screen_space_factor: f32,
6570}
6571
6572#[derive(Clone, Debug, PartialEq)]
6573pub enum TessSpacing { Equal, FractionalEven, FractionalOdd }
6574#[derive(Clone, Debug, PartialEq)]
6575pub enum TessWinding { Ccw, Cw }
6576#[derive(Clone, Debug, PartialEq)]
6577pub enum TessPrimitive { Triangles, Quads, Isolines }
6578
6579impl TessellationConfig {
6580 pub fn new() -> Self { Self { enabled: false, patch_size: 3, outer_level: [4.0; 4], inner_level: [4.0; 2], spacing: TessSpacing::Equal, winding: TessWinding::Ccw, primitive: TessPrimitive::Triangles, point_mode: false, adaptive: false, max_tess_level: 64.0, screen_space_factor: 1.0 } }
6581 pub fn displacement_mapping() -> Self { Self { enabled: true, adaptive: true, ..Self::new() } }
6582 pub fn lod_triangles(level: f32) -> Self { Self { enabled: true, outer_level: [level; 4], inner_level: [level; 2], ..Self::new() } }
6583 pub fn disabled() -> Self { Self { enabled: false, ..Self::new() } }
6584 pub fn effective_patch_vertices(&self) -> u32 {
6585 match self.primitive { TessPrimitive::Triangles => 3, TessPrimitive::Quads => 4, TessPrimitive::Isolines => 2 }
6586 }
6587}
6588
6589impl Default for TessellationConfig { fn default() -> Self { Self::new() } }
6590
6591#[derive(Clone, Debug)]
6594pub struct WireframeConfig {
6595 pub enabled: bool,
6596 pub color: [f32; 4],
6597 pub line_width: f32,
6598 pub overlay_on_solid: bool,
6599 pub show_normals: bool,
6600 pub normal_length: f32,
6601 pub normal_color: [f32; 4],
6602 pub show_tangents: bool,
6603 pub highlight_selected: bool,
6604 pub selection_color: [f32; 4],
6605}
6606
6607impl WireframeConfig {
6608 pub fn new() -> Self { Self { enabled: false, color: [0.0, 1.0, 0.0, 1.0], line_width: 1.0, overlay_on_solid: true, show_normals: false, normal_length: 0.1, normal_color: [0.0, 0.0, 1.0, 1.0], show_tangents: false, highlight_selected: true, selection_color: [1.0, 0.5, 0.0, 1.0] } }
6609 pub fn enable(&mut self) { self.enabled = true; }
6610 pub fn disable(&mut self) { self.enabled = false; }
6611 pub fn show_normals(mut self) -> Self { self.show_normals = true; self }
6612}
6613
6614impl Default for WireframeConfig { fn default() -> Self { Self::new() } }
6615
6616#[derive(Clone, Debug)]
6619pub struct ShaderFallback {
6620 pub trigger_on_error: bool,
6621 pub fallback_program_id: u32,
6622 pub fallback_name: String,
6623 pub auto_compile_fallback: bool,
6624 pub notify_on_fallback: bool,
6625}
6626
6627impl ShaderFallback {
6628 pub fn new(fallback_id: u32) -> Self { Self { trigger_on_error: true, fallback_program_id: fallback_id, fallback_name: "error_fallback".into(), auto_compile_fallback: true, notify_on_fallback: true } }
6629 pub fn with_name(mut self, name: impl Into<String>) -> Self { self.fallback_name = name.into(); self }
6630 pub fn silent(mut self) -> Self { self.notify_on_fallback = false; self }
6631}
6632
6633impl Default for ShaderFallback { fn default() -> Self { Self::new(0) } }
6634
6635pub const TESSELLATION_MAX_PATCH_SIZE: u32 = 32;
6638pub const TESSELLATION_MAX_LEVEL: f32 = 64.0;
6639pub const GEOMETRY_SHADER_MAX_VERTICES_OUT: u32 = 256;
6640pub const GEOMETRY_SHADER_MAX_INVOCATIONS: u32 = 32;
6641pub const WIREFRAME_LINE_WIDTH_MAX: f32 = 8.0;
6642pub const SHADER_FALLBACK_MAX_DEPTH: u32 = 3;
6643
6644pub fn geometry_primitive_name(p: &GeometryPrimitive) -> &'static str {
6645 match p { GeometryPrimitive::Points => "points", GeometryPrimitive::Lines => "lines", GeometryPrimitive::LinesAdjacency => "lines_adjacency", GeometryPrimitive::Triangles => "triangles", GeometryPrimitive::TrianglesAdjacency => "triangles_adjacency", GeometryPrimitive::LineStrip => "line_strip", GeometryPrimitive::TriangleStrip => "triangle_strip" }
6646}
6647
6648pub fn tess_spacing_name(s: &TessSpacing) -> &'static str {
6649 match s { TessSpacing::Equal => "equal_spacing", TessSpacing::FractionalEven => "fractional_even_spacing", TessSpacing::FractionalOdd => "fractional_odd_spacing" }
6650}
6651
6652pub fn glsl_layout_tess_ctrl(patch_size: u32) -> String { format!("layout(vertices = {}) out;", patch_size) }
6653pub fn glsl_layout_tess_eval(primitive: &TessPrimitive, spacing: &TessSpacing, winding: &TessWinding) -> String {
6654 format!("layout({}, {}, {}) in;", match primitive { TessPrimitive::Triangles => "triangles", TessPrimitive::Quads => "quads", TessPrimitive::Isolines => "isolines" }, tess_spacing_name(spacing), match winding { TessWinding::Ccw => "ccw", TessWinding::Cw => "cw" })
6655}
6656pub fn glsl_layout_geometry(input: &GeometryPrimitive, output: &GeometryPrimitive, max_verts: u32) -> String {
6657 format!("layout({}) in;\nlayout({}, max_vertices = {}) out;", geometry_primitive_name(input), geometry_primitive_name(output), max_verts)
6658}
6659
6660pub fn shader_complete_feature_list() -> Vec<String> {
6661 let mut features = shader_compiler_module_list().iter().map(|s| s.to_string()).collect::<Vec<_>>();
6662 features.extend(all_post_process_effect_names().iter().map(|s| s.to_string()));
6663 features.extend(all_glsl_keywords().iter().map(|s| s.to_string()));
6664 features
6665}
6666pub fn shader_feature_count() -> usize { shader_complete_feature_list().len() }
6667
6668
6669#[derive(Clone, Debug, Default)]
6672pub struct ShaderSystemState {
6673 pub initialized: bool,
6674 pub programs_loaded: u32,
6675 pub materials_loaded: u32,
6676 pub textures_loaded: u32,
6677 pub compile_errors: u32,
6678 pub compile_warnings: u32,
6679 pub last_error: Option<String>,
6680 pub uptime_frames: u64,
6681 pub total_draw_calls: u64,
6682 pub total_triangles: u64,
6683}
6684
6685impl ShaderSystemState {
6686 pub fn new() -> Self { Self { initialized: false, ..Default::default() } }
6687 pub fn initialize(&mut self) { self.initialized = true; }
6688 pub fn record_draw(&mut self, triangles: u64) { self.total_draw_calls += 1; self.total_triangles += triangles; }
6689 pub fn record_compile_error(&mut self, msg: impl Into<String>) { self.compile_errors += 1; self.last_error = Some(msg.into()); }
6690 pub fn record_compile_warning(&mut self) { self.compile_warnings += 1; }
6691 pub fn tick_frame(&mut self) { self.uptime_frames += 1; }
6692 pub fn is_healthy(&self) -> bool { self.initialized && self.compile_errors == 0 }
6693 pub fn avg_tris_per_call(&self) -> f64 { if self.total_draw_calls == 0 { 0.0 } else { self.total_triangles as f64 / self.total_draw_calls as f64 } }
6694 pub fn summary(&self) -> String {
6695 format!("ShaderSystem: init={}, programs={}, materials={}, textures={}, errors={}, warnings={}", self.initialized, self.programs_loaded, self.materials_loaded, self.textures_loaded, self.compile_errors, self.compile_warnings)
6696 }
6697}
6698
6699pub fn builtin_pbr_vertex_shader() -> &'static str {
6702 r#"#version 450
6703layout(location=0) in vec3 a_position;
6704layout(location=1) in vec3 a_normal;
6705layout(location=2) in vec2 a_uv;
6706layout(location=3) in vec4 a_tangent;
6707layout(std140, binding=0) uniform PerFrame { mat4 view; mat4 proj; mat4 view_proj; vec4 camera_pos; float time; float delta_time; float near_plane; float far_plane; };
6708layout(std140, binding=1) uniform PerObject { mat4 model; mat4 mvp; mat4 normal_matrix; };
6709layout(location=0) out vec3 v_world_pos;
6710layout(location=1) out vec3 v_normal;
6711layout(location=2) out vec2 v_uv;
6712layout(location=3) out vec3 v_tangent;
6713layout(location=4) out vec3 v_bitangent;
6714void main() {
6715 vec4 world_pos = model * vec4(a_position, 1.0);
6716 v_world_pos = world_pos.xyz;
6717 v_normal = normalize((normal_matrix * vec4(a_normal, 0.0)).xyz);
6718 v_tangent = normalize((normal_matrix * vec4(a_tangent.xyz, 0.0)).xyz);
6719 v_bitangent = cross(v_normal, v_tangent) * a_tangent.w;
6720 v_uv = a_uv;
6721 gl_Position = view_proj * world_pos;
6722}"#
6723}
6724
6725pub fn builtin_pbr_fragment_shader() -> &'static str {
6726 r#"#version 450
6727layout(location=0) in vec3 v_world_pos;
6728layout(location=1) in vec3 v_normal;
6729layout(location=2) in vec2 v_uv;
6730layout(location=3) in vec3 v_tangent;
6731layout(location=4) in vec3 v_bitangent;
6732layout(std140, binding=2) uniform PerMaterial { vec4 albedo_color; vec4 emissive_color; float roughness; float metalness; float normal_scale; float occlusion_strength; float emissive_intensity; float opacity; };
6733layout(binding=0) uniform sampler2D u_albedo;
6734layout(binding=1) uniform sampler2D u_normal_map;
6735layout(binding=2) uniform sampler2D u_orm;
6736layout(location=0) out vec4 out_color;
6737const float PI = 3.14159265;
6738void main() {
6739 vec3 albedo = texture(u_albedo, v_uv).rgb * albedo_color.rgb;
6740 vec3 orm = texture(u_orm, v_uv).rgb;
6741 float ao = orm.r; float roughness_v = orm.g * roughness; float metalness_v = orm.b * metalness;
6742 vec3 N = normalize(v_normal);
6743 vec3 emit = emissive_color.rgb * emissive_intensity;
6744 out_color = vec4(albedo + emit, opacity);
6745}"#
6746}
6747
6748pub fn builtin_unlit_vertex_shader() -> &'static str {
6749 r#"#version 450
6750layout(location=0) in vec3 a_position;
6751layout(location=2) in vec2 a_uv;
6752layout(std140, binding=1) uniform PerObject { mat4 mvp; mat4 model; mat4 normal_matrix; };
6753layout(location=0) out vec2 v_uv;
6754void main() { v_uv = a_uv; gl_Position = mvp * vec4(a_position, 1.0); }"#
6755}
6756
6757pub fn builtin_unlit_fragment_shader() -> &'static str {
6758 r#"#version 450
6759layout(location=0) in vec2 v_uv;
6760layout(binding=0) uniform sampler2D u_texture;
6761layout(std140, binding=2) uniform PerMaterial { vec4 albedo_color; vec4 emissive_color; float roughness; float metalness; float normal_scale; float occlusion_strength; float emissive_intensity; float opacity; };
6762layout(location=0) out vec4 out_color;
6763void main() { out_color = texture(u_texture, v_uv) * albedo_color; }"#
6764}
6765
6766pub const SHADER_SYSTEM_STATE_VERSION: u32 = 1;
6767pub const BUILTIN_PBR_SHADER_STAGES: usize = 2;
6768pub const BUILTIN_UNLIT_SHADER_STAGES: usize = 2;
6769pub const BUILTIN_SHADER_COUNT: usize = 4;
6770
6771pub fn builtin_shader_names() -> &'static [&'static str] { &["pbr_vertex", "pbr_fragment", "unlit_vertex", "unlit_fragment"] }
6772pub fn get_builtin_shader(name: &str) -> Option<&'static str> {
6773 match name {
6774 "pbr_vertex" => Some(builtin_pbr_vertex_shader()),
6775 "pbr_fragment" => Some(builtin_pbr_fragment_shader()),
6776 "unlit_vertex" => Some(builtin_unlit_vertex_shader()),
6777 "unlit_fragment" => Some(builtin_unlit_fragment_shader()),
6778 _ => None,
6779 }
6780}
6781pub fn shader_editor_complete_build_info() -> String {
6782 format!("ShaderCompilerEditor Complete Build — {} builtin shaders, {} intrinsic docs, {} module total", BUILTIN_SHADER_COUNT, SHADER_GLSL_INTRINSIC_DOC_COUNT, shader_module_count())
6783}
6784
6785
6786pub fn blend_factor_to_str(f: &BlendFactor) -> &'static str {
6789 match f {
6790 BlendFactor::Zero => "Zero",
6791 BlendFactor::One => "One",
6792 BlendFactor::SrcAlpha => "SrcAlpha",
6793 BlendFactor::OneMinusSrcAlpha => "OneMinusSrcAlpha",
6794 BlendFactor::DstAlpha => "DstAlpha",
6795 BlendFactor::OneMinusDstAlpha => "OneMinusDstAlpha",
6796 BlendFactor::SrcColor => "SrcColor",
6797 BlendFactor::OneMinusSrcColor => "OneMinusSrcColor",
6798 BlendFactor::ConstantAlpha => "ConstantAlpha",
6799 }
6800}
6801
6802pub fn compare_func_to_str(f: &CompareFunc) -> &'static str {
6803 match f {
6804 CompareFunc::Never => "Never",
6805 CompareFunc::Less => "Less",
6806 CompareFunc::Equal => "Equal",
6807 CompareFunc::LessEqual => "LessEqual",
6808 CompareFunc::Greater => "Greater",
6809 CompareFunc::NotEqual => "NotEqual",
6810 CompareFunc::GreaterEqual => "GreaterEqual",
6811 CompareFunc::Always => "Always",
6812 }
6813}
6814
6815pub fn cull_mode_to_str(m: &CullMode) -> &'static str {
6816 match m {
6817 CullMode::None => "None",
6818 CullMode::Front => "Front",
6819 CullMode::Back => "Back",
6820 CullMode::FrontAndBack => "FrontAndBack",
6821 }
6822}
6823
6824pub fn polygon_mode_to_str(m: &PolygonMode) -> &'static str {
6825 match m { PolygonMode::Fill => "Fill", PolygonMode::Line => "Line", PolygonMode::Point => "Point" }
6826}
6827
6828pub fn attachment_format_is_depth(f: &AttachmentFormat) -> bool {
6829 matches!(f, AttachmentFormat::Depth16 | AttachmentFormat::Depth24 | AttachmentFormat::Depth32F | AttachmentFormat::Depth24Stencil8)
6830}
6831
6832pub fn attachment_format_channel_count(f: &AttachmentFormat) -> u32 {
6833 match f {
6834 AttachmentFormat::Rgba8 | AttachmentFormat::Rgba16F | AttachmentFormat::Rgba32F => 4,
6835 AttachmentFormat::Rg8 | AttachmentFormat::Rg16F => 2,
6836 AttachmentFormat::R8 | AttachmentFormat::R16F | AttachmentFormat::R32F => 1,
6837 AttachmentFormat::Depth16 | AttachmentFormat::Depth24 | AttachmentFormat::Depth32F => 1,
6838 AttachmentFormat::Depth24Stencil8 => 2,
6839 }
6840}
6841
6842pub fn buffer_usage_to_str(u: &BufferUsage) -> &'static str {
6843 match u {
6844 BufferUsage::Vertex => "Vertex",
6845 BufferUsage::Index => "Index",
6846 BufferUsage::Uniform => "Uniform",
6847 BufferUsage::Storage => "Storage",
6848 BufferUsage::Indirect => "Indirect",
6849 BufferUsage::Staging => "Staging",
6850 }
6851}
6852
6853pub fn sampler_type_to_str(t: &SamplerType) -> &'static str {
6854 match t {
6855 SamplerType::Linear => "Linear",
6856 SamplerType::Nearest => "Nearest",
6857 SamplerType::LinearMipmap => "LinearMipmap",
6858 SamplerType::Trilinear => "Trilinear",
6859 SamplerType::Anisotropic => "Anisotropic",
6860 }
6861}
6862
6863pub fn render_pass_type_to_str(t: &RenderPassType) -> &'static str {
6864 match t {
6865 RenderPassType::Opaque => "Opaque",
6866 RenderPassType::Transparent => "Transparent",
6867 RenderPassType::Shadow => "Shadow",
6868 RenderPassType::PostProcess => "PostProcess",
6869 RenderPassType::Ui => "UI",
6870 RenderPassType::Compute => "Compute",
6871 RenderPassType::Custom => "Custom",
6872 }
6873}
6874
6875pub fn post_effect_type_to_str(t: &PostEffectType) -> &'static str {
6876 match t {
6877 PostEffectType::Bloom => "Bloom",
6878 PostEffectType::Tonemap => "Tonemap",
6879 PostEffectType::Vignette => "Vignette",
6880 PostEffectType::ChromaticAberration => "ChromaticAberration",
6881 PostEffectType::DepthOfField => "DepthOfField",
6882 PostEffectType::MotionBlur => "MotionBlur",
6883 PostEffectType::Ssao => "SSAO",
6884 PostEffectType::Fxaa => "FXAA",
6885 PostEffectType::Custom => "Custom",
6886 }
6887}
6888
6889pub fn tone_mapping_mode_to_str(m: &ToneMappingMode) -> &'static str {
6890 match m {
6891 ToneMappingMode::None => "None",
6892 ToneMappingMode::Reinhard => "Reinhard",
6893 ToneMappingMode::Aces => "ACES",
6894 ToneMappingMode::Filmic => "Filmic",
6895 ToneMappingMode::Uncharted2 => "Uncharted2",
6896 ToneMappingMode::Custom => "Custom",
6897 }
6898}
6899
6900pub fn lighting_model_type_to_str(t: &LightingModelType) -> &'static str {
6901 match t {
6902 LightingModelType::Pbr => "PBR",
6903 LightingModelType::Phong => "Phong",
6904 LightingModelType::BlinnPhong => "BlinnPhong",
6905 LightingModelType::Lambert => "Lambert",
6906 LightingModelType::Toon => "Toon",
6907 LightingModelType::Unlit => "Unlit",
6908 LightingModelType::Custom(_) => "Custom",
6909 }
6910}
6911
6912pub fn descriptor_type_to_str(t: &DescriptorType) -> &'static str {
6913 match t {
6914 DescriptorType::UniformBuffer => "UniformBuffer",
6915 DescriptorType::StorageBuffer => "StorageBuffer",
6916 DescriptorType::Sampler => "Sampler",
6917 DescriptorType::SampledImage => "SampledImage",
6918 DescriptorType::StorageImage => "StorageImage",
6919 DescriptorType::CombinedImageSampler => "CombinedImageSampler",
6920 DescriptorType::InputAttachment => "InputAttachment",
6921 }
6922}
6923
6924pub fn render_graph_node_type_to_str(t: &RenderGraphNodeType) -> &'static str {
6925 match t {
6926 RenderGraphNodeType::Pass => "Pass",
6927 RenderGraphNodeType::Blit => "Blit",
6928 RenderGraphNodeType::Compute => "Compute",
6929 RenderGraphNodeType::Present => "Present",
6930 RenderGraphNodeType::Upload => "Upload",
6931 RenderGraphNodeType::Barrier => "Barrier",
6932 RenderGraphNodeType::Custom(_) => "Custom",
6933 }
6934}
6935
6936pub fn bloom_mode_to_str(m: &BloomMode) -> &'static str {
6937 match m {
6938 BloomMode::Classic => "Classic",
6939 BloomMode::Kawase => "Kawase",
6940 BloomMode::Dual => "Dual",
6941 BloomMode::Convolution => "Convolution",
6942 }
6943}
6944
6945pub fn ssao_quality_to_str(q: &SsaoQuality) -> &'static str {
6946 match q {
6947 SsaoQuality::Low => "Low",
6948 SsaoQuality::Medium => "Medium",
6949 SsaoQuality::High => "High",
6950 SsaoQuality::Ultra => "Ultra",
6951 }
6952}
6953
6954pub fn anti_aliasing_mode_to_str(m: &AntiAliasingMode) -> &'static str {
6955 match m {
6956 AntiAliasingMode::None => "None",
6957 AntiAliasingMode::Msaa => "MSAA",
6958 AntiAliasingMode::Fxaa => "FXAA",
6959 AntiAliasingMode::Taa => "TAA",
6960 AntiAliasingMode::Smaa => "SMAA",
6961 AntiAliasingMode::Dlss => "DLSS",
6962 }
6963}
6964
6965pub fn optimization_level_to_str(o: &OptimizationLevel) -> &'static str {
6966 match o {
6967 OptimizationLevel::None => "None",
6968 OptimizationLevel::Low => "Low",
6969 OptimizationLevel::Medium => "Medium",
6970 OptimizationLevel::High => "High",
6971 OptimizationLevel::Aggressive => "Aggressive",
6972 }
6973}
6974
6975pub fn code_gen_target_to_str(t: &CodeGenTarget) -> &'static str {
6976 match t {
6977 CodeGenTarget::Glsl450 => "GLSL 4.50",
6978 CodeGenTarget::Glsl300Es => "GLSL 3.00 ES",
6979 CodeGenTarget::Wgsl => "WGSL",
6980 CodeGenTarget::Hlsl50 => "HLSL 5.0",
6981 CodeGenTarget::Msl20 => "Metal SL 2.0",
6982 }
6983}
6984
6985pub fn shader_export_format_to_str(f: &ShaderExportFormat) -> &'static str {
6986 match f {
6987 ShaderExportFormat::Glsl => "GLSL",
6988 ShaderExportFormat::SpirV => "SPIR-V",
6989 ShaderExportFormat::Wgsl => "WGSL",
6990 ShaderExportFormat::Json => "JSON",
6991 ShaderExportFormat::Binary => "Binary",
6992 }
6993}
6994
6995pub fn stencil_op_to_str(op: &StencilOp) -> &'static str {
6996 match op {
6997 StencilOp::Keep => "Keep", StencilOp::Zero => "Zero", StencilOp::Replace => "Replace",
6998 StencilOp::IncrClamp => "IncrClamp", StencilOp::DecrClamp => "DecrClamp",
6999 StencilOp::Invert => "Invert", StencilOp::IncrWrap => "IncrWrap", StencilOp::DecrWrap => "DecrWrap",
7000 }
7001}
7002
7003pub fn tess_primitive_to_str(p: &TessPrimitive) -> &'static str {
7004 match p { TessPrimitive::Triangles => "triangles", TessPrimitive::Quads => "quads", TessPrimitive::Isolines => "isolines" }
7005}
7006
7007pub fn tess_winding_to_str(w: &TessWinding) -> &'static str {
7008 match w { TessWinding::Ccw => "ccw", TessWinding::Cw => "cw" }
7009}
7010
7011pub const SHADER_COMPILER_FULL_VERSION: &str = "ShaderCompilerEditor v2.0.0-proof-engine";
7014pub const SHADER_COMPILER_BUILD_DATE: &str = "2026-03-29";
7015pub const SHADER_COMPILER_AUTHOR: &str = "proof-engine rendering team";
7016pub const SHADER_MAX_MATERIALS_PER_PASS: usize = 256;
7017pub const SHADER_MAX_LIGHTS: usize = 512;
7018pub const SHADER_MAX_SHADOW_CASCADES: usize = 4;
7019pub const SHADER_MAX_REFLECTION_PROBES: usize = 64;
7020pub const SHADER_COMPUTE_MAX_WORKGROUP_SIZE: u32 = 1024;
7021pub const SHADER_MAX_PUSH_CONSTANT_BYTES: u32 = 128;
7022pub const SHADER_MAX_DESCRIPTOR_SETS: u32 = 4;
7023pub const SHADER_MAX_BINDINGS_PER_SET: u32 = 16;
7024
7025pub fn shader_compiler_credits() -> &'static str {
7026 "ShaderCompilerEditor — Built for proof-engine. Supports GLSL, HLSL, WGSL, SPIR-V, and Metal SL."
7027}
7028
7029pub fn shader_pipeline_summary(manager: &ShaderManager) -> String {
7030 format!(
7031 "Programs: {} | Errors: {} | Quality: {} | Frames: {}",
7032 manager.program_count(),
7033 manager.error_count(),
7034 manager.quality_preset,
7035 manager.frame_executor.frame_number(),
7036 )
7037}
7038