#!/usr/bin/env -S jai -quiet -optimized shaderview.jai - shader_thing: Shader_Thing; shader_path: string; last_mtime: Apollo_Time; last_size: s64; elapsed: float; frame_time: float; revision: int; args_is_shadertoy: bool; SCREEN_X :s32: 1280; SCREEN_Y :s32: 720; TITLE :: "shader viewer"; SHADERTOY_COMPAT :: #string STR_END #version 330 in vec2 fragTexCoord; out vec4 finalColor; uniform vec2 iResolution; uniform float iTime; uniform vec2 iMouse; STR_END; SHADERTOY_MAIN :: #string STR_END void main() { mainImage(finalColor, fragTexCoord * iResolution); } STR_END; ARGS_HELP :: #string STR_END Usage: shaderview [options] Options: -st Pass it, when you're using a shader from Shadertoy.com STR_END; Shader_Load_Kind :: enum_flags { NONE; PATH; CODE; SHADERTOY; } Shader_Thing :: struct { using shader: Shader; resolution: s32; time: s32; mouse: s32; } main :: () { args := get_command_line_arguments(); if args.count == 1 { log_error("No arguments passed. Need file path to fragment shader."); log_error("`./shaderview `"); exit(1); } for string.[ "help", "h", "-help", "-h", "--help" ] { if array_find(args, it) { log(ARGS_HELP); exit(0); } } shader_path = args[1]; if !file_exists(shader_path) { log_error("Cringe bro. Path does not exist: '%'", shader_path); exit(1); } args_is_shadertoy = array_find(args, "-st"); if args_is_shadertoy then log("Loaded Shadertoy flavor"); SetConfigFlags(.WINDOW_RESIZABLE | .VSYNC_HINT); SetTraceLogLevel(.WARNING); InitWindow(SCREEN_X, SCREEN_Y, TITLE); SetTargetFPS(60); shader_ok := load_shader(*shader_thing, shader_path, .PATH); target := LoadRenderTexture(SCREEN_X, SCREEN_Y); while !WindowShouldClose() { push_allocator(temp); reset_temporary_storage(); frame_time = GetFrameTime(); elapsed += frame_time; if IsKeyPressed(.R) || has_file_changed(shader_path) { kind := Shader_Load_Kind.PATH; if args_is_shadertoy then kind |= .SHADERTOY; shader_ok = load_shader(*shader_thing, shader_path, kind); elapsed = 0.0; if shader_ok then revision += 1; } if shader_ok then update_uniforms(*shader_thing); if IsWindowResized() { UnloadRenderTexture(target); target = LoadRenderTexture(SCREEN_X, SCREEN_Y); } { BeginDrawing(); defer EndDrawing(); ClearBackground(BLACK); if shader_ok { BeginShaderMode(shader_thing.shader); DrawTextureRec( target.texture, { 0, 0, cast(float, GetRenderWidth()), cast(float, -GetRenderHeight()) }, { 0, 0 }, WHITE ); EndShaderMode(); } else { ClearBackground({ 20, 20, 20, 255 }); draw_text_in_center("Shader compile error", 30, RED); draw_text_in_center("check console logs", 20, GRAY, { 0, 40 }); } DrawFPS(8, 8); path_c := temp_c_string(shader_path); tw := MeasureText(path_c, 14); DrawText( path_c, GetScreenWidth() - tw - 8, 8, 14, { 200, 200, 200, 180 } ); rev := tprint("Revision Count: %", revision); crev := temp_c_string(rev); DrawText(crev, 8, GetScreenHeight() - 22, 13, { 160, 160, 160, 180 }); } } UnloadShader(shader_thing.shader); UnloadRenderTexture(target); CloseWindow(); } draw_text_in_center :: ( text: string, size: s32, color: Color, offset: struct { x, y: s32; } = {} ) { mid_x := GetScreenWidth() / 2; mid_y := GetScreenHeight() / 2; mid_x += offset.x; mid_y += offset.y; ctext := temp_c_string(text); mt := MeasureText(ctext, size); DrawText(ctext, mid_x - (mt / 2), mid_y, size, color); } has_file_changed :: (path: string) -> bool { mtime, size := file_modtime_and_size(path); if mtime != last_mtime || size != last_size { last_mtime = mtime; last_size = size; return true; } return false; } load_shader :: ( using sh: *Shader_Thing, path_or_code: string, kind: Shader_Load_Kind ) -> ok: bool { assert(!(!kind || kind & .NONE), "cannot be `NONE`"); buf: String_Builder; append(*buf, SHADERTOY_COMPAT); if kind & .PATH { if !file_exists(path_or_code) return false; raw, ok := read_entire_file(path_or_code); if !ok { log_error("Could not read '%'", path_or_code); return false; } append(*buf, raw); } else if kind & .CODE { append(*buf, path_or_code); } if kind & .SHADERTOY { append(*buf, SHADERTOY_MAIN); } if id > 0 then UnloadShader(shader); code := builder_to_string(*buf); poc := to_c_string(code); shader = LoadShaderFromMemory(null, poc); resolution = GetShaderLocation(shader, "iResolution"); time = GetShaderLocation(shader, "iTime"); mouse = GetShaderLocation(shader, "iMouse"); return id > 0; } update_uniforms :: (using sh: *Shader_Thing) { res: [2]float; res[0] = cast(float) GetScreenWidth(); res[1] = cast(float) GetScreenHeight(); t := elapsed; mp := GetMousePosition(); mouse_pos: [2]float; mouse_pos[0] = mp.x; mouse_pos[1] = mp.y; SetShaderValue(shader, resolution, res.data, .VEC2); SetShaderValue(shader, time, *t, .FLOAT); SetShaderValue(shader, mouse, mouse_pos.data, .VEC2); } #import "Basic"; #import "Print_Vars"; #import "File"; #import "File_Utilities"; #import "raylib";