diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index 5b0ecf07b..1bc4bfd1d 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -257,7 +257,9 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal { errdefer buf_instance.deinit(); // Initialize our shaders - var shaders = try Shaders.init(alloc, device, &.{}); + var shaders = try Shaders.init(alloc, device, &.{ + @embedFile("shaders/temp3.metal"), + }); errdefer shaders.deinit(alloc); // Font atlas textures @@ -584,6 +586,36 @@ pub fn drawFrame(self: *Metal, surface: *apprt.Surface) !void { // Get our drawable (CAMetalDrawable) const drawable = self.swapchain.msgSend(objc.Object, objc.sel("nextDrawable"), .{}); + // Make our intermediate texture + const target = target: { + const desc = init: { + const Class = objc.getClass("MTLTextureDescriptor").?; + const id_alloc = Class.msgSend(objc.Object, objc.sel("alloc"), .{}); + const id_init = id_alloc.msgSend(objc.Object, objc.sel("init"), .{}); + break :init id_init; + }; + + // Set our properties + desc.setProperty("pixelFormat", @intFromEnum(mtl.MTLPixelFormat.bgra8unorm)); + desc.setProperty("width", @as(c_ulong, @intCast(self.screen_size.?.width))); + desc.setProperty("height", @as(c_ulong, @intCast(self.screen_size.?.height))); + desc.setProperty( + "usage", + @intFromEnum(mtl.MTLTextureUsage.render_target) | + @intFromEnum(mtl.MTLTextureUsage.shader_read) | + @intFromEnum(mtl.MTLTextureUsage.shader_write), + ); + + const id = self.device.msgSend( + ?*anyopaque, + objc.sel("newTextureWithDescriptor:"), + .{desc}, + ) orelse return error.MetalFailed; + + break :target objc.Object.fromId(id); + }; + defer target.msgSend(void, objc.sel("release"), .{}); + // If our font atlas changed, sync the texture data if (self.font_group.atlas_greyscale.modified) { try syncAtlasTexture(self.device, &self.font_group.atlas_greyscale, &self.texture_greyscale); @@ -620,10 +652,10 @@ pub fn drawFrame(self: *Metal, surface: *apprt.Surface) !void { // Ghostty in XCode in debug mode it returns a CaptureMTLDrawable // which ironically doesn't implement CAMetalDrawable as a // property so we just send a message. - const texture = drawable.msgSend(objc.c.id, objc.sel("texture"), .{}); + //const texture = drawable.msgSend(objc.c.id, objc.sel("texture"), .{}); attachment.setProperty("loadAction", @intFromEnum(mtl.MTLLoadAction.clear)); attachment.setProperty("storeAction", @intFromEnum(mtl.MTLStoreAction.store)); - attachment.setProperty("texture", texture); + attachment.setProperty("texture", target.value); attachment.setProperty("clearColor", mtl.MTLClearColor{ .red = @as(f32, @floatFromInt(self.current_background_color.r)) / 255, .green = @as(f32, @floatFromInt(self.current_background_color.g)) / 255, @@ -659,10 +691,132 @@ pub fn drawFrame(self: *Metal, surface: *apprt.Surface) !void { try self.drawImagePlacements(encoder, self.image_placements.items[self.image_text_end..]); } + { + // MTLRenderPassDescriptor + const desc = desc: { + const MTLRenderPassDescriptor = objc.getClass("MTLRenderPassDescriptor").?; + const desc = MTLRenderPassDescriptor.msgSend( + objc.Object, + objc.sel("renderPassDescriptor"), + .{}, + ); + + // Set our color attachment to be our drawable surface. + const attachments = objc.Object.fromId(desc.getProperty(?*anyopaque, "colorAttachments")); + { + const attachment = attachments.msgSend( + objc.Object, + objc.sel("objectAtIndexedSubscript:"), + .{@as(c_ulong, 0)}, + ); + + // Texture is a property of CAMetalDrawable but if you run + // Ghostty in XCode in debug mode it returns a CaptureMTLDrawable + // which ironically doesn't implement CAMetalDrawable as a + // property so we just send a message. + const texture = drawable.msgSend(objc.c.id, objc.sel("texture"), .{}); + attachment.setProperty("loadAction", @intFromEnum(mtl.MTLLoadAction.clear)); + attachment.setProperty("storeAction", @intFromEnum(mtl.MTLStoreAction.store)); + attachment.setProperty("texture", texture); + attachment.setProperty("clearColor", mtl.MTLClearColor{ + .red = 0, + .green = 0, + .blue = 0, + .alpha = 1, + }); + } + + break :desc desc; + }; + + // MTLRenderCommandEncoder + const encoder = buffer.msgSend( + objc.Object, + objc.sel("renderCommandEncoderWithDescriptor:"), + .{desc.value}, + ); + defer encoder.msgSend(void, objc.sel("endEncoding"), .{}); + + try self.drawPostShader(encoder, target.value); + } + buffer.msgSend(void, objc.sel("presentDrawable:"), .{drawable.value}); buffer.msgSend(void, objc.sel("commit"), .{}); } +var post_time: f32 = 1; + +fn drawPostShader( + self: *Metal, + encoder: objc.Object, + texture: objc.c.id, +) !void { + // Use our image shader pipeline + encoder.msgSend( + void, + objc.sel("setRenderPipelineState:"), + .{self.shaders.post_pipelines[0].value}, + ); + + // Set our uniform, which is the only shared buffer + encoder.msgSend( + void, + objc.sel("setVertexBytes:length:atIndex:"), + .{ + @as(*const anyopaque, @ptrCast(&self.uniforms)), + @as(c_ulong, @sizeOf(@TypeOf(self.uniforms))), + @as(c_ulong, 1), + }, + ); + + const Buffer = mtl_buffer.Buffer(mtl_shaders.PostUniforms); + var buf = try Buffer.initFill(self.device, &.{.{ + .resolution = .{ + @floatFromInt(self.screen_size.?.width), + @floatFromInt(self.screen_size.?.height), + 1, + }, + .time = post_time, + .time_delta = 1, + .frame_rate = 1, + .frame = 1, + .channel_time = [1][4]f32{.{ 0, 0, 0, 0 }} ** 4, + .channel_resolution = [1][4]f32{.{ 0, 0, 0, 0 }} ** 4, + .mouse = .{ 0, 0, 0, 0 }, + .date = .{ 0, 0, 0, 0 }, + .sample_rate = 1, + }}); + defer buf.deinit(); + post_time += 1; + + // Set our buffer + encoder.msgSend( + void, + objc.sel("setFragmentBuffer:offset:atIndex:"), + .{ buf.buffer.value, @as(c_ulong, 0), @as(c_ulong, 0) }, + ); + + encoder.msgSend( + void, + objc.sel("setFragmentTexture:atIndex:"), + .{ + texture, + @as(c_ulong, 0), + }, + ); + + // Draw! + encoder.msgSend( + void, + objc.sel("drawPrimitives:vertexStart:vertexCount:"), + .{ + @intFromEnum(mtl.MTLPrimitiveType.triangle_strip), + @as(c_ulong, 0), + @as(c_ulong, 4), + }, + ); +} + fn drawImagePlacements( self: *Metal, encoder: objc.Object, diff --git a/src/renderer/metal/api.zig b/src/renderer/metal/api.zig index f3dc2f835..1c9f175ac 100644 --- a/src/renderer/metal/api.zig +++ b/src/renderer/metal/api.zig @@ -57,6 +57,7 @@ pub const MTLVertexStepFunction = enum(c_ulong) { /// https://developer.apple.com/documentation/metal/mtlpixelformat?language=objc pub const MTLPixelFormat = enum(c_ulong) { r8unorm = 10, + rgba8unorm = 70, rgba8uint = 73, bgra8unorm = 80, }; @@ -98,6 +99,15 @@ pub const MTLBlendOperation = enum(c_ulong) { max = 4, }; +/// https://developer.apple.com/documentation/metal/mtltextureusage?language=objc +pub const MTLTextureUsage = enum(c_ulong) { + unknown = 0, + shader_read = 1, + shader_write = 2, + render_target = 4, + pixel_format_view = 8, +}; + /// https://developer.apple.com/documentation/metal/mtlresourceoptions?language=objc /// (incomplete, we only use this mode so we just hardcode it) pub const MTLResourceStorageModeShared: c_ulong = @intFromEnum(MTLStorageMode.shared) << 4; diff --git a/src/renderer/metal/shaders.zig b/src/renderer/metal/shaders.zig index 05b9993b4..4edb7eb90 100644 --- a/src/renderer/metal/shaders.zig +++ b/src/renderer/metal/shaders.zig @@ -49,6 +49,7 @@ pub const Shaders = struct { const post_pipelines: []const objc.Object = initPostPipelines( alloc, device, + library, post_shaders, ) catch |err| err: { // If an error happens while building postprocess shaders we @@ -126,6 +127,20 @@ pub const Uniforms = extern struct { strikethrough_thickness: f32, }; +/// The uniforms used for custom postprocess shaders. +pub const PostUniforms = extern struct { + resolution: [3]f32 align(16), + time: f32 align(4), + time_delta: f32 align(4), + frame_rate: f32 align(4), + frame: i32 align(4), + channel_time: [4][4]f32 align(16), + channel_resolution: [4][4]f32 align(16), + mouse: [4]f32 align(16), + date: [4]f32 align(16), + sample_rate: f32 align(4), +}; + /// Initialize the MTLLibrary. A MTLLibrary is a collection of shaders. fn initLibrary(device: objc.Object) !objc.Object { // Hardcoded since this file isn't meant to be reusable. @@ -157,6 +172,7 @@ fn initLibrary(device: objc.Object) !objc.Object { fn initPostPipelines( alloc: Allocator, device: objc.Object, + library: objc.Object, shaders: []const [:0]const u8, ) ![]const objc.Object { // If we have no shaders, do nothing. @@ -177,7 +193,7 @@ fn initPostPipelines( // Build each shader. Note we don't use "0.." to build our index // because we need to keep track of our length to clean up above. for (shaders) |source| { - pipelines[i] = try initPostPipeline(device, source); + pipelines[i] = try initPostPipeline(device, library, source); i += 1; } @@ -185,9 +201,13 @@ fn initPostPipelines( } /// Initialize a single custom shader pipeline from shader source. -fn initPostPipeline(device: objc.Object, data: [:0]const u8) !objc.Object { +fn initPostPipeline( + device: objc.Object, + library: objc.Object, + data: [:0]const u8, +) !objc.Object { // Create our library which has the shader source - const library = library: { + const post_library = library: { const source = try macos.foundation.String.createWithBytes( data, .utf8, @@ -196,22 +216,75 @@ fn initPostPipeline(device: objc.Object, data: [:0]const u8) !objc.Object { defer source.release(); var err: ?*anyopaque = null; - const library = device.msgSend( + const post_library = device.msgSend( objc.Object, objc.sel("newLibraryWithSource:options:error:"), .{ source, @as(?*anyopaque, null), &err }, ); try checkError(err); - errdefer library.msgSend(void, objc.sel("release"), .{}); + errdefer post_library.msgSend(void, objc.sel("release"), .{}); - break :library library; + break :library post_library; }; - // TODO: need to do this once we set the pipeline - //defer library.msgSend(void, objc.sel("release"), .{}); + defer post_library.msgSend(void, objc.sel("release"), .{}); - // TODO: need to implement the actual pipeline + // Get our vertex and fragment functions + const func_vert = func_vert: { + const str = try macos.foundation.String.createWithBytes( + "post_vertex", + .utf8, + false, + ); + defer str.release(); - return library; + const ptr = library.msgSend(?*anyopaque, objc.sel("newFunctionWithName:"), .{str}); + break :func_vert objc.Object.fromId(ptr.?); + }; + const func_frag = func_frag: { + const str = try macos.foundation.String.createWithBytes( + "main0", + .utf8, + false, + ); + defer str.release(); + + const ptr = post_library.msgSend(?*anyopaque, objc.sel("newFunctionWithName:"), .{str}); + break :func_frag objc.Object.fromId(ptr.?); + }; + + // Create our descriptor + const desc = init: { + const Class = objc.getClass("MTLRenderPipelineDescriptor").?; + const id_alloc = Class.msgSend(objc.Object, objc.sel("alloc"), .{}); + const id_init = id_alloc.msgSend(objc.Object, objc.sel("init"), .{}); + break :init id_init; + }; + desc.setProperty("vertexFunction", func_vert); + desc.setProperty("fragmentFunction", func_frag); + + // Set our color attachment + const attachments = objc.Object.fromId(desc.getProperty(?*anyopaque, "colorAttachments")); + { + const attachment = attachments.msgSend( + objc.Object, + objc.sel("objectAtIndexedSubscript:"), + .{@as(c_ulong, 0)}, + ); + + // Value is MTLPixelFormatBGRA8Unorm + attachment.setProperty("pixelFormat", @as(c_ulong, 80)); + } + + // Make our state + var err: ?*anyopaque = null; + const pipeline_state = device.msgSend( + objc.Object, + objc.sel("newRenderPipelineStateWithDescriptor:error:"), + .{ desc, &err }, + ); + try checkError(err); + + return pipeline_state; } /// Initialize the cell render pipeline for our shader library. diff --git a/src/renderer/shaders/cell.metal b/src/renderer/shaders/cell.metal index 8c1e0750a..e6ba3f7ac 100644 --- a/src/renderer/shaders/cell.metal +++ b/src/renderer/shaders/cell.metal @@ -253,3 +253,20 @@ fragment float4 image_fragment( uint4 rgba = image.sample(textureSampler, in.tex_coord); return float4(rgba) / 255.0f; } + +//------------------------------------------------------------------- +// Post Shader +//------------------------------------------------------------------- +#pragma mark - Post Shader + +struct PostVertexOut { + float4 position [[ position ]]; +}; + +constant float2 post_pos[4] = { {-1,-1}, {1,-1}, {-1,1}, {1,1 } }; + +vertex PostVertexOut post_vertex(uint id [[ vertex_id ]]) { + PostVertexOut out; + out.position = float4(post_pos[id], 0, 1); + return out; +} diff --git a/src/renderer/shaders/temp3.metal b/src/renderer/shaders/temp3.metal new file mode 100644 index 000000000..c8cedc0d4 --- /dev/null +++ b/src/renderer/shaders/temp3.metal @@ -0,0 +1,116 @@ +#pragma clang diagnostic ignored "-Wmissing-prototypes" + +#include +#include + +using namespace metal; + +// Implementation of the GLSL mod() function, which is slightly different than Metal fmod() +template +inline Tx mod(Tx x, Ty y) +{ + return x - y * floor(x / y); +} + +struct Globals +{ + float3 iResolution; + float iTime; + float iTimeDelta; + float iFrameRate; + int iFrame; + float4 iChannelTime[4]; + float3 iChannelResolution[4]; + float4 iMouse; + float4 iDate; + float iSampleRate; +}; + +struct main0_out +{ + float4 _fragColor [[color(0)]]; +}; + +static inline __attribute__((always_inline)) +float2 curve(thread float2& uv) +{ + uv = (uv - float2(0.5)) * 2.0; + uv *= 1.10000002384185791015625; + 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 / float2(2.0)) + float2(0.5); + uv = (uv * 0.920000016689300537109375) + float2(0.039999999105930328369140625); + return uv; +} + +static inline __attribute__((always_inline)) +void mainImage(thread float4& fragColor, thread const float2& fragCoord, constant Globals& _89, texture2d iChannel0, sampler iChannel0Smplr) +{ + float2 q = fragCoord / float2(_89.iResolution[0], _89.iResolution[1]); + float2 uv = q; + float2 param = uv; + float2 _100 = curve(param); + uv = _100; + float3 oricol = iChannel0.sample(iChannel0Smplr, float2(q.x, q.y)).xyz; + float x = ((sin((0.300000011920928955078125 * _89.iTime) + (uv.y * 21.0)) * sin((0.699999988079071044921875 * _89.iTime) + (uv.y * 29.0))) * sin((0.300000011920928955078125 + (0.3300000131130218505859375 * _89.iTime)) + (uv.y * 31.0))) * 0.001700000022538006305694580078125; + float3 col; + col.x = iChannel0.sample(iChannel0Smplr, float2((x + uv.x) + 0.001000000047497451305389404296875, uv.y + 0.001000000047497451305389404296875)).x + 0.0500000007450580596923828125; + col.y = iChannel0.sample(iChannel0Smplr, float2((x + uv.x) + 0.0, uv.y - 0.00200000009499490261077880859375)).y + 0.0500000007450580596923828125; + col.z = iChannel0.sample(iChannel0Smplr, float2((x + uv.x) - 0.00200000009499490261077880859375, uv.y + 0.0)).z + 0.0500000007450580596923828125; + col.x += (0.07999999821186065673828125 * iChannel0.sample(iChannel0Smplr, ((float2(x + 0.02500000037252902984619140625, -0.02700000070035457611083984375) * 0.75) + float2(uv.x + 0.001000000047497451305389404296875, uv.y + 0.001000000047497451305389404296875))).x); + col.y += (0.0500000007450580596923828125 * iChannel0.sample(iChannel0Smplr, ((float2(x + (-0.02199999988079071044921875), -0.0199999995529651641845703125) * 0.75) + float2(uv.x + 0.0, uv.y - 0.00200000009499490261077880859375))).y); + col.z += (0.07999999821186065673828125 * iChannel0.sample(iChannel0Smplr, ((float2(x + (-0.0199999995529651641845703125), -0.017999999225139617919921875) * 0.75) + float2(uv.x - 0.00200000009499490261077880859375, uv.y + 0.0))).z); + col = fast::clamp((col * 0.60000002384185791015625) + (((col * 0.4000000059604644775390625) * col) * 1.0), float3(0.0), float3(1.0)); + float vig = 0.0 + ((((16.0 * uv.x) * uv.y) * (1.0 - uv.x)) * (1.0 - uv.y)); + col *= float3(pow(vig, 0.300000011920928955078125)); + col *= float3(0.949999988079071044921875, 1.0499999523162841796875, 0.949999988079071044921875); + col *= 2.7999999523162841796875; + float scans = fast::clamp(0.3499999940395355224609375 + (0.3499999940395355224609375 * sin((3.5 * _89.iTime) + ((uv.y * _89.iResolution[1u]) * 1.5))), 0.0, 1.0); + float s = pow(scans, 1.7000000476837158203125); + col *= float3(0.4000000059604644775390625 + (0.699999988079071044921875 * s)); + col *= (1.0 + (0.00999999977648258209228515625 * sin(110.0 * _89.iTime))); + bool _352 = uv.x < 0.0; + bool _359; + if (!_352) + { + _359 = uv.x > 1.0; + } + else + { + _359 = _352; + } + if (_359) + { + col *= 0.0; + } + bool _366 = uv.y < 0.0; + bool _373; + if (!_366) + { + _373 = uv.y > 1.0; + } + else + { + _373 = _366; + } + if (_373) + { + col *= 0.0; + } + col *= (float3(1.0) - (float3(fast::clamp((mod(fragCoord.x, 2.0) - 1.0) * 2.0, 0.0, 1.0)) * 0.64999997615814208984375)); + float comp = smoothstep(0.100000001490116119384765625, 0.89999997615814208984375, sin(_89.iTime)); + fragColor = float4(col, 1.0); +} + +fragment main0_out main0(constant Globals& _89 [[buffer(0)]], texture2d iChannel0 [[texture(0)]], float4 gl_FragCoord [[position]]) +{ + constexpr sampler iChannel0Smplr(address::clamp_to_edge, filter::linear); + + main0_out out = {}; + float2 param_1 = gl_FragCoord.xy; + float4 param; + mainImage(param, param_1, _89, iChannel0, iChannel0Smplr); + out._fragColor = param; + return out; +} +