summaryrefslogtreecommitdiff
path: root/src/effect_sources.zig
blob: 8702627863e0973b0abfa735f39f02b68f115d99 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
//! Build-embedded source behind EffectCode.
//!
//! Several builtins intentionally share one shader pass. Returning segments
//! makes that sharing visible instead of copying or manufacturing a pretend
//! per-effect program.

const std = @import("std");
const build_config = @import("pardes_config");
const runtime_config = @import("runtime_config.zig");

const panel_math = @embedFile("panel_animation.zig");
const tty_compositor = @embedFile("tty/panel_compositor.zig");
const panel_vertex = @embedFile("effect-source-ui.vert.glsl");
const panel_fragment = @embedFile("effect-source-ui.frag.glsl");
const image_vertex = @embedFile("effect-source-image.vert.glsl");
const image_fragment = @embedFile("effect-source-image.frag.glsl");
const scene_vertex = @embedFile("effect-source-crt.vert.glsl");
const scene_fragment = @embedFile("effect-source-crt.frag.glsl");
const gui_scene_mapping = @embedFile("gui/crt.zig");
const gui_host = @embedFile("gui/gui.zig");
const mac_scene = @embedFile("effect-source-crt.ci.metal");
const mac_postprocessor = @embedFile("macos/Sources/ScenePostprocessor.swift");
const mac_view = @embedFile("macos/Sources/PardesView.swift");

fn sourceSection(
    comptime source: []const u8,
    comptime begin_marker: []const u8,
    comptime end_marker: []const u8,
) []const u8 {
    // gui.zig is intentionally embedded as the source actually compiled, then
    // narrowed to its marked host paths. The comptime byte scan is larger than
    // Zig's small default quota but contributes no runtime work.
    @setEvalBranchQuota(1_000_000);
    const begin = std.mem.indexOf(u8, source, begin_marker) orelse
        @compileError("EffectCode source begin marker is missing");
    const body = begin + begin_marker.len;
    const end = std.mem.indexOfPos(u8, source, body, end_marker) orelse
        @compileError("EffectCode source end marker is missing");
    return source[body..end];
}

const gui_panel_host = sourceSection(
    gui_host,
    "// EFFECT_CODE_PANEL_HOST_BEGIN\n",
    "// EFFECT_CODE_PANEL_HOST_END",
);
const gui_native_panel = sourceSection(
    gui_host,
    "// EFFECT_CODE_NATIVE_PANEL_BEGIN\n",
    "// EFFECT_CODE_NATIVE_PANEL_END",
);
const gui_frame_submission = sourceSection(
    gui_host,
    "// EFFECT_CODE_FRAME_SUBMISSION_BEGIN\n",
    "// EFFECT_CODE_FRAME_SUBMISSION_END",
);
const gui_cell_instance = sourceSection(
    gui_host,
    "// EFFECT_CODE_CELL_INSTANCE_BEGIN\n",
    "// EFFECT_CODE_CELL_INSTANCE_END",
);

const ui_vertex_path = if (build_config.gui_shader_sources_prebuilt)
    "shaders/prebuilt/ui.vert.glsl"
else
    "shaders/ui.vert.glsl";
const ui_fragment_path = if (build_config.gui_shader_sources_prebuilt)
    "shaders/prebuilt/ui.frag.glsl"
else
    "shaders/ui.frag.glsl";
const image_vertex_path = if (build_config.gui_shader_sources_prebuilt)
    "shaders/prebuilt/image.vert.glsl"
else
    "shaders/image.vert.glsl";
const image_fragment_path = if (build_config.gui_shader_sources_prebuilt)
    "shaders/prebuilt/image.frag.glsl"
else
    "shaders/image.frag.glsl";
const scene_vertex_path = if (build_config.gui_shader_sources_prebuilt)
    "shaders/prebuilt/crt.vert.glsl"
else
    "shaders/crt.vert.glsl";
const scene_fragment_path = if (build_config.gui_shader_sources_prebuilt)
    "shaders/prebuilt/crt.frag.glsl"
else
    "shaders/crt.frag.glsl";

pub const Backend = @TypeOf(build_config.platform);
pub const backend: Backend = build_config.platform;

pub const GuiShaderSourceMode = enum {
    live,
    prebuilt_snapshot,

    pub fn label(mode: GuiShaderSourceMode) []const u8 {
        return switch (mode) {
            .live => "live GLSL compiled during this build",
            .prebuilt_snapshot => "paired GLSL snapshot for committed SPIR-V",
        };
    }
};

/// Only the SDL backend consumes GLSL/SPIR-V. Other backends deliberately have
/// no shader-source mode rather than being mislabeled "live" or "prebuilt".
pub fn guiShaderSourceMode() ?GuiShaderSourceMode {
    if (backend != .gui) return null;
    return if (build_config.gui_shader_sources_prebuilt)
        .prebuilt_snapshot
    else
        .live;
}

pub const Segment = struct {
    path: []const u8,
    source: []const u8,
};

const tty_panel = [_]Segment{
    .{ .path = "src/panel_animation.zig", .source = panel_math },
    .{ .path = "src/tty/panel_compositor.zig", .source = tty_compositor },
};
const gui_panel = [_]Segment{
    .{ .path = "src/panel_animation.zig", .source = panel_math },
    .{ .path = "src/gui/gui.zig#panel-host", .source = gui_panel_host },
    .{ .path = "src/gui/gui.zig#native-panel", .source = gui_native_panel },
    .{ .path = "src/gui/gui.zig#frame-submission", .source = gui_frame_submission },
    .{ .path = "src/gui/gui.zig#cell-instance", .source = gui_cell_instance },
    .{ .path = ui_vertex_path, .source = panel_vertex },
    .{ .path = ui_fragment_path, .source = panel_fragment },
    .{ .path = image_vertex_path, .source = image_vertex },
    .{ .path = image_fragment_path, .source = image_fragment },
};
const gui_scene = [_]Segment{
    .{ .path = "src/gui/crt.zig", .source = gui_scene_mapping },
    .{ .path = "src/gui/gui.zig#frame-submission", .source = gui_frame_submission },
    .{ .path = scene_vertex_path, .source = scene_vertex },
    .{ .path = scene_fragment_path, .source = scene_fragment },
};
const mac_panel = [_]Segment{
    .{ .path = "src/panel_animation.zig", .source = panel_math },
    .{ .path = "src/macos/Sources/PardesView.swift", .source = mac_view },
    .{ .path = "src/macos/Sources/ScenePostprocessor.swift", .source = mac_postprocessor },
    .{ .path = "shaders/crt.ci.metal", .source = mac_scene },
};
const mac_scene_segments = [_]Segment{
    .{ .path = "src/macos/Sources/PardesView.swift", .source = mac_view },
    .{ .path = "src/macos/Sources/ScenePostprocessor.swift", .source = mac_postprocessor },
    .{ .path = "shaders/crt.ci.metal", .source = mac_scene },
};

/// Sources for the backend this executable was built for. Keeping the backend
/// compile-time and out of the call signature is important: a TTY binary must
/// not carry the macOS host and Metal source merely because another switch arm
/// could have returned them.
pub fn forSetting(setting: runtime_config.Setting) ?[]const Segment {
    return switch (setting.action) {
        .transition => switch (backend) {
            .tty => &tty_panel,
            .gui => &gui_panel,
            .macos => &mac_panel,
            .web => null,
        },
        .scene => switch (backend) {
            .gui => &gui_scene,
            .macos => &mac_scene_segments,
            .tty, .web => null,
        },
        else => null,
    };
}

test "every current-backend effect exposes embedded implementation code" {
    for (runtime_config.settings) |setting| switch (setting.action) {
        .transition => if (backend != .web) {
            const segments = forSetting(setting) orelse return error.MissingTransitionSource;
            for (segments) |segment| {
                try std.testing.expect(segment.path.len > 0);
                try std.testing.expect(segment.source.len > 0);
            }
        } else try std.testing.expect(forSetting(setting) == null),
        .scene => if (backend == .gui or backend == .macos) {
            const segments = forSetting(setting) orelse return error.MissingSceneSource;
            for (segments) |segment| {
                try std.testing.expect(segment.path.len > 0);
                try std.testing.expect(segment.source.len > 0);
            }
        } else try std.testing.expect(forSetting(setting) == null),
        else => {},
    };
}

test "GUI scene EffectCode includes both runtime shader stages" {
    if (comptime backend == .gui) {
        try std.testing.expectEqual(@as(usize, 4), gui_scene.len);
        try std.testing.expectEqualStrings("src/gui/crt.zig", gui_scene[0].path);
        try std.testing.expectEqualStrings("src/gui/gui.zig#frame-submission", gui_scene[1].path);
        try std.testing.expectEqualStrings(scene_vertex_path, gui_scene[2].path);
        try std.testing.expectEqualStrings(scene_fragment_path, gui_scene[3].path);
        try std.testing.expect(std.mem.indexOf(u8, gui_scene[0].source, "pub fn mapScene") != null);
        try std.testing.expect(std.mem.indexOf(u8, gui_scene[1].source, "SDL_PushGPUFragmentUniformData") != null);
        try std.testing.expect(std.mem.indexOf(u8, gui_scene[2].source, "gl_VertexIndex") != null);
        try std.testing.expect(std.mem.indexOf(u8, gui_scene[3].source, "u_crt.effects") != null);
    }
}

test "GUI panel EffectCode includes the actual host paint and submission path" {
    if (comptime backend == .gui) {
        try std.testing.expect(std.mem.indexOf(u8, gui_panel_host, "fn makePaintPlan") != null);
        try std.testing.expect(std.mem.indexOf(u8, gui_panel_host, "fn setTransitionFields") != null);
        try std.testing.expect(std.mem.indexOf(u8, gui_native_panel, "fn prepareNativeImages") != null);
        try std.testing.expect(std.mem.indexOf(u8, gui_native_panel, "fn drawNativeImagesGpu") != null);
        try std.testing.expect(std.mem.indexOf(u8, gui_native_panel, "active.visualBox()") != null);
        try std.testing.expect(std.mem.indexOf(u8, gui_frame_submission, "for (paint_plan.batches") != null);
        try std.testing.expect(std.mem.indexOf(u8, gui_cell_instance, "fn emitInstance") != null);
        try std.testing.expect(std.mem.indexOf(u8, gui_cell_instance, "setTransitionFields") != null);
        try std.testing.expect(std.mem.indexOf(u8, image_fragment, "vec3 contrastInk") != null);
    }
}

test "shader source provenance is GUI-only and follows the build input" {
    if (comptime backend == .gui) {
        const expected: GuiShaderSourceMode = if (build_config.gui_shader_sources_prebuilt)
            .prebuilt_snapshot
        else
            .live;
        try std.testing.expectEqual(expected, guiShaderSourceMode().?);
        try std.testing.expect(expected.label().len > 0);
    } else try std.testing.expect(guiShaderSourceMode() == null);
}

test "mac EffectCode includes the runtime panel and scene wiring" {
    if (comptime backend == .macos) {
        try std.testing.expect(std.mem.indexOf(u8, mac_scene, "extern \"C\" float4 pardesPanelClear") != null);
        try std.testing.expect(std.mem.indexOf(u8, mac_scene, "extern \"C\" float4 pardesPanel") != null);
        try std.testing.expect(std.mem.indexOf(u8, mac_scene, "extern \"C\" float4 pardesScene") != null);
        try std.testing.expect(std.mem.indexOf(u8, mac_scene, "float3 contrastInk") != null);
        try std.testing.expect(std.mem.indexOf(u8, mac_view, "hideCursor: !tracks.isEmpty") != null);
        try std.testing.expect(std.mem.indexOf(u8, mac_postprocessor, "panelClearKernel.apply") != null);
        try std.testing.expect(std.mem.indexOf(u8, mac_postprocessor, "panelKernel.apply") != null);
        try std.testing.expect(std.mem.indexOf(u8, mac_postprocessor, "sceneKernel.apply") != null);
    }
}