0#!/usr/bin/env -S jai -quiet -optimized shaderview.jai -
3shader_thing: Shader_Thing;
4shader_path: string;
5last_mtime: Apollo_Time;
6last_size: s64;
7elapsed: float;
8frame_time: float;
9revision: int;
11args_is_shadertoy: bool;
14SCREEN_X :s32: 1280;
15SCREEN_Y :s32: 720;
17TITLE :: "shader viewer";
19SHADERTOY_COMPAT :: #string STR_END
20#version 330
21in vec2 fragTexCoord;
22out vec4 finalColor;
23uniform vec2 iResolution;
24uniform float iTime;
25uniform vec2 iMouse;
26STR_END;
28SHADERTOY_MAIN :: #string STR_END
29void main() {
30 mainImage(finalColor, fragTexCoord * iResolution);
31}
32STR_END;
34ARGS_HELP :: #string STR_END
35Usage: shaderview <path to fragment shader> [options]
37Options:
38 -st Pass it, when you're using a shader from Shadertoy.com
40STR_END;
43Shader_Load_Kind :: enum_flags {
44 NONE;
45 PATH;
46 CODE;
47 SHADERTOY;
48}
51Shader_Thing :: struct {
52 using shader: Shader;
53 resolution: s32;
54 time: s32;
55 mouse: s32;
56}
59main :: () {
60 args := get_command_line_arguments();
62 if args.count == 1 {
63 log_error("No arguments passed. Need file path to fragment shader.");
64 log_error("`./shaderview <path to shader>`");
65 exit(1);
66 }
68 for string.[ "help", "h", "-help", "-h", "--help" ] {
69 if array_find(args, it) {
70 log(ARGS_HELP);
71 exit(0);
72 }
73 }
75 shader_path = args[1];
77 if !file_exists(shader_path) {
78 log_error("Cringe bro. Path does not exist: '%'", shader_path);
79 exit(1);
80 }
82 args_is_shadertoy = array_find(args, "-st");
83 if args_is_shadertoy then log("Loaded Shadertoy flavor");
86 SetConfigFlags(.WINDOW_RESIZABLE | .VSYNC_HINT);
87 SetTraceLogLevel(.WARNING);
88 InitWindow(SCREEN_X, SCREEN_Y, TITLE);
89 SetTargetFPS(60);
91 shader_ok := load_shader(*shader_thing, shader_path, .PATH);
92 target := LoadRenderTexture(SCREEN_X, SCREEN_Y);
94 while !WindowShouldClose() {
95 push_allocator(temp);
96 reset_temporary_storage();
97 frame_time = GetFrameTime();
98 elapsed += frame_time;
100 if IsKeyPressed(.R) || has_file_changed(shader_path) {
101 kind := Shader_Load_Kind.PATH;
102 if args_is_shadertoy then kind |= .SHADERTOY;
103 shader_ok = load_shader(*shader_thing, shader_path, kind);
104 elapsed = 0.0;
105 if shader_ok then revision += 1;
106 }
108 if shader_ok then update_uniforms(*shader_thing);
110 if IsWindowResized() {
111 UnloadRenderTexture(target);
112 target = LoadRenderTexture(SCREEN_X, SCREEN_Y);
113 }
115 {
116 BeginDrawing();
117 defer EndDrawing();
119 ClearBackground(BLACK);
121 if shader_ok {
122 BeginShaderMode(shader_thing.shader);
123 DrawTextureRec(
124 target.texture,
125 { 0, 0, cast(float, GetRenderWidth()), cast(float, -GetRenderHeight()) },
126 { 0, 0 },
127 WHITE
128 );
129 EndShaderMode();
130 }
131 else {
132 ClearBackground({ 20, 20, 20, 255 });
133 draw_text_in_center("Shader compile error", 30, RED);
134 draw_text_in_center("check console logs", 20, GRAY, { 0, 40 });
135 }
137 DrawFPS(8, 8);
138 path_c := temp_c_string(shader_path);
139 tw := MeasureText(path_c, 14);
140 DrawText(
141 path_c,
142 GetScreenWidth() - tw - 8, 8, 14,
143 { 200, 200, 200, 180 }
144 );
146 rev := tprint("Revision Count: %", revision);
147 crev := temp_c_string(rev);
148 DrawText(crev, 8, GetScreenHeight() - 22, 13, { 160, 160, 160, 180 });
149 }
150 }
152 UnloadShader(shader_thing.shader);
153 UnloadRenderTexture(target);
154 CloseWindow();
155}
157draw_text_in_center :: (
158 text: string,
159 size: s32,
160 color: Color,
161 offset: struct { x, y: s32; } = {}
162) {
163 mid_x := GetScreenWidth() / 2;
164 mid_y := GetScreenHeight() / 2;
166 mid_x += offset.x;
167 mid_y += offset.y;
169 ctext := temp_c_string(text);
170 mt := MeasureText(ctext, size);
171 DrawText(ctext, mid_x - (mt / 2), mid_y, size, color);
172}
174has_file_changed :: (path: string) -> bool {
175 mtime, size := file_modtime_and_size(path);
176 if mtime != last_mtime || size != last_size {
177 last_mtime = mtime;
178 last_size = size;
179 return true;
180 }
181 return false;
182}
184load_shader :: (
185 using sh: *Shader_Thing,
186 path_or_code: string,
187 kind: Shader_Load_Kind
188)
189 -> ok: bool
190{
191 assert(!(!kind || kind & .NONE), "cannot be `NONE`");
193 buf: String_Builder;
195 append(*buf, SHADERTOY_COMPAT);
197 if kind & .PATH {
198 if !file_exists(path_or_code) return false;
199 raw, ok := read_entire_file(path_or_code);
200 if !ok {
201 log_error("Could not read '%'", path_or_code);
202 return false;
203 }
204 append(*buf, raw);
205 }
206 else if kind & .CODE {
207 append(*buf, path_or_code);
208 }
210 if kind & .SHADERTOY {
211 append(*buf, SHADERTOY_MAIN);
212 }
214 if id > 0 then UnloadShader(shader);
216 code := builder_to_string(*buf);
217 poc := to_c_string(code);
219 shader = LoadShaderFromMemory(null, poc);
220 resolution = GetShaderLocation(shader, "iResolution");
221 time = GetShaderLocation(shader, "iTime");
222 mouse = GetShaderLocation(shader, "iMouse");
224 return id > 0;
225}
227update_uniforms :: (using sh: *Shader_Thing) {
228 res: [2]float;
229 res[0] = cast(float) GetScreenWidth();
230 res[1] = cast(float) GetScreenHeight();
232 t := elapsed;
234 mp := GetMousePosition();
235 mouse_pos: [2]float;
236 mouse_pos[0] = mp.x;
237 mouse_pos[1] = mp.y;
239 SetShaderValue(shader, resolution, res.data, .VEC2);
240 SetShaderValue(shader, time, *t, .FLOAT);
241 SetShaderValue(shader, mouse, mouse_pos.data, .VEC2);
242}
245#import "Basic";
246#import "Print_Vars";
247#import "File";
248#import "File_Utilities";
250#import "raylib";
index : shaderview
Low effort shader viewer