renderer: shadertoy functions

This commit is contained in:
Mitchell Hashimoto
2023-11-16 09:22:36 -08:00
parent 3a4aef2dcd
commit 1bd92619b1
7 changed files with 243 additions and 1 deletions

View File

@ -1,4 +1,5 @@
pub const c = @import("c.zig");
pub const testing = @import("test.zig");
pub usingnamespace @import("init.zig");
pub usingnamespace @import("program.zig");
pub usingnamespace @import("shader.zig");

View File

@ -35,7 +35,7 @@ pub const Program = opaque {
}
pub fn spirvGetPtr(self: *Program) ![*]u8 {
return c.glslang_program_SPIRV_get_ptr(@ptrCast(self));
return @ptrCast(c.glslang_program_SPIRV_get_ptr(@ptrCast(self)));
}
pub fn spirvGetMessages(self: *Program) ![:0]const u8 {

View File

@ -15,6 +15,7 @@ const WasmTarget = @import("os/wasm/target.zig").Target;
pub usingnamespace @import("renderer/cursor.zig");
pub usingnamespace @import("renderer/message.zig");
pub usingnamespace @import("renderer/size.zig");
pub const shadertoy = @import("renderer/shadertoy.zig");
pub const Metal = @import("renderer/Metal.zig");
pub const OpenGL = @import("renderer/OpenGL.zig");
pub const WebGL = @import("renderer/WebGL.zig");

View File

@ -0,0 +1,27 @@
#version 430 core
layout(binding = 0) uniform Globals {
uniform vec3 iResolution;
uniform float iTime;
uniform float iTimeDelta;
uniform float iFrameRate;
uniform int iFrame;
uniform float iChannelTime[4];
uniform vec3 iChannelResolution[4];
uniform vec4 iMouse;
uniform vec4 iDate;
uniform float iSampleRate;
};
layout(binding = 0) uniform sampler2D iChannel0;
layout(binding = 1) uniform sampler2D iChannel1;
layout(binding = 2) uniform sampler2D iChannel2;
layout(binding = 3) uniform sampler2D iChannel3;
layout(location = 0) in vec4 gl_FragCoord;
layout(location = 0) out vec4 _fragColor;
#define texture2D texture
void mainImage( out vec4 fragColor, in vec2 fragCoord );
void main() { mainImage (_fragColor, gl_FragCoord.xy); }

View File

@ -0,0 +1,59 @@
// This shader is NOT BUILD INTO Ghostty. This is only here for unit tests.
// Loosely based on postprocessing shader by inigo quilez, License Creative
// Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
vec2 curve(vec2 uv)
{
uv = (uv - 0.5) * 2.0;
uv *= 1.1;
uv.x *= 1.0 + pow((abs(uv.y) / 5.0), 2.0);
uv.y *= 1.0 + pow((abs(uv.x) / 4.0), 2.0);
uv = (uv / 2.0) + 0.5;
uv = uv *0.92 + 0.04;
return uv;
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 q = fragCoord.xy / iResolution.xy;
vec2 uv = q;
uv = curve( uv );
vec3 oricol = texture( iChannel0, vec2(q.x,q.y) ).xyz;
vec3 col;
float x = sin(0.3*iTime+uv.y*21.0)*sin(0.7*iTime+uv.y*29.0)*sin(0.3+0.33*iTime+uv.y*31.0)*0.0017;
col.r = texture(iChannel0,vec2(x+uv.x+0.001,uv.y+0.001)).x+0.05;
col.g = texture(iChannel0,vec2(x+uv.x+0.000,uv.y-0.002)).y+0.05;
col.b = texture(iChannel0,vec2(x+uv.x-0.002,uv.y+0.000)).z+0.05;
col.r += 0.08*texture(iChannel0,0.75*vec2(x+0.025, -0.027)+vec2(uv.x+0.001,uv.y+0.001)).x;
col.g += 0.05*texture(iChannel0,0.75*vec2(x+-0.022, -0.02)+vec2(uv.x+0.000,uv.y-0.002)).y;
col.b += 0.08*texture(iChannel0,0.75*vec2(x+-0.02, -0.018)+vec2(uv.x-0.002,uv.y+0.000)).z;
col = clamp(col*0.6+0.4*col*col*1.0,0.0,1.0);
float vig = (0.0 + 1.0*16.0*uv.x*uv.y*(1.0-uv.x)*(1.0-uv.y));
col *= vec3(pow(vig,0.3));
col *= vec3(0.95,1.05,0.95);
col *= 2.8;
float scans = clamp( 0.35+0.35*sin(3.5*iTime+uv.y*iResolution.y*1.5), 0.0, 1.0);
float s = pow(scans,1.7);
col = col*vec3( 0.4+0.7*s) ;
col *= 1.0+0.01*sin(110.0*iTime);
if (uv.x < 0.0 || uv.x > 1.0)
col *= 0.0;
if (uv.y < 0.0 || uv.y > 1.0)
col *= 0.0;
col*=1.0-0.65*vec3(clamp((mod(fragCoord.x, 2.0)-1.0)*2.0,0.0,1.0));
float comp = smoothstep( 0.1, 0.9, sin(iTime) );
// Remove the next line to stop cross-fade between original and postprocess
// col = mix( col, oricol, comp );
fragColor = vec4(col,1.0);
}

View File

@ -0,0 +1,12 @@
vec2 curve(vec2 uv)
{
uv = (uv - 0.5) * 2.0;
uv *= 1.1;
uv.x *= 1.0 + pow((abs(uv.y) / 5.0), 2.0);
uv.y *= 1.0 + pow((abs(uv.x) / 4.0), 2.0);
uv = (uv / 2.0) + 0.5;
uv = uv *0.92 + 0.04;
return uv;
}
// Missing mainImage!

142
src/renderer/shadertoy.zig Normal file
View File

@ -0,0 +1,142 @@
const std = @import("std");
const builtin = @import("builtin");
const Allocator = std.mem.Allocator;
const glslang = @import("glslang");
/// Convert a ShaderToy shader into valid GLSL.
///
/// ShaderToy shaders aren't full shaders, they're just implementing a
/// mainImage function and don't define any of the uniforms. This function
/// will convert the ShaderToy shader into a valid GLSL shader that can be
/// compiled and linked.
pub fn glslFromShader(writer: anytype, src: []const u8) !void {
const prefix = @embedFile("shaders/shadertoy_prefix.glsl");
try writer.writeAll(prefix);
try writer.writeAll("\n\n");
try writer.writeAll(src);
}
/// Convert a GLSL shader into SPIR-V assembly.
pub fn spirvFromGlsl(
writer: anytype,
errlog: ?*SpirvLog,
src: [:0]const u8,
) !void {
// So we can run unit tests without fear.
if (builtin.is_test) try glslang.testing.ensureInit();
const c = glslang.c;
const input: c.glslang_input_t = .{
.language = c.GLSLANG_SOURCE_GLSL,
.stage = c.GLSLANG_STAGE_FRAGMENT,
.client = c.GLSLANG_CLIENT_VULKAN,
.client_version = c.GLSLANG_TARGET_VULKAN_1_2,
.target_language = c.GLSLANG_TARGET_SPV,
.target_language_version = c.GLSLANG_TARGET_SPV_1_5,
.code = src.ptr,
.default_version = 100,
.default_profile = c.GLSLANG_NO_PROFILE,
.force_default_version_and_profile = 0,
.forward_compatible = 0,
.messages = c.GLSLANG_MSG_DEFAULT_BIT,
.resource = c.glslang_default_resource(),
};
const shader = try glslang.Shader.create(&input);
defer shader.delete();
shader.preprocess(&input) catch |err| {
if (errlog) |ptr| ptr.fromShader(shader) catch {};
return err;
};
shader.parse(&input) catch |err| {
if (errlog) |ptr| ptr.fromShader(shader) catch {};
return err;
};
const program = try glslang.Program.create();
defer program.delete();
program.addShader(shader);
program.link(
c.GLSLANG_MSG_SPV_RULES_BIT |
c.GLSLANG_MSG_VULKAN_RULES_BIT,
) catch |err| {
if (errlog) |ptr| ptr.fromProgram(program) catch {};
return err;
};
program.spirvGenerate(c.GLSLANG_STAGE_FRAGMENT);
const size = program.spirvGetSize();
const ptr = try program.spirvGetPtr();
try writer.writeAll(ptr[0..size]);
}
/// Retrieve errors from spirv compilation.
pub const SpirvLog = struct {
alloc: Allocator,
info: [:0]const u8 = "",
debug: [:0]const u8 = "",
pub fn deinit(self: *const SpirvLog) void {
if (self.info.len > 0) self.alloc.free(self.info);
if (self.debug.len > 0) self.alloc.free(self.debug);
}
fn fromShader(self: *SpirvLog, shader: *glslang.Shader) !void {
const info = try shader.getInfoLog();
const debug = try shader.getDebugInfoLog();
self.info = "";
self.debug = "";
if (info.len > 0) self.info = try self.alloc.dupeZ(u8, info);
if (debug.len > 0) self.debug = try self.alloc.dupeZ(u8, debug);
}
fn fromProgram(self: *SpirvLog, program: *glslang.Program) !void {
const info = try program.getInfoLog();
const debug = try program.getDebugInfoLog();
self.info = "";
self.debug = "";
if (info.len > 0) self.info = try self.alloc.dupeZ(u8, info);
if (debug.len > 0) self.debug = try self.alloc.dupeZ(u8, debug);
}
};
/// Convert ShaderToy shader to null-terminated glsl for testing.
fn testGlslZ(alloc: Allocator, src: []const u8) ![:0]const u8 {
var list = std.ArrayList(u8).init(alloc);
defer list.deinit();
try glslFromShader(list.writer(), src);
return try list.toOwnedSliceSentinel(0);
}
test "spirv" {
const testing = std.testing;
const alloc = testing.allocator;
const src = try testGlslZ(alloc, test_crt);
defer alloc.free(src);
var buf: [4096]u8 = undefined;
var buf_stream = std.io.fixedBufferStream(&buf);
const writer = buf_stream.writer();
try spirvFromGlsl(writer, null, src);
}
test "spirv invalid" {
const testing = std.testing;
const alloc = testing.allocator;
const src = try testGlslZ(alloc, test_invalid);
defer alloc.free(src);
var buf: [4096]u8 = undefined;
var buf_stream = std.io.fixedBufferStream(&buf);
const writer = buf_stream.writer();
var errlog: SpirvLog = .{ .alloc = alloc };
defer errlog.deinit();
try testing.expectError(error.GlslangFailed, spirvFromGlsl(writer, &errlog, src));
try testing.expect(errlog.info.len > 0);
}
const test_crt = @embedFile("shaders/test_shadertoy_crt.glsl");
const test_invalid = @embedFile("shaders/test_shadertoy_invalid.glsl");