aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authors-ol <s+removethis@s-ol.nu>2025-03-20 13:39:03 +0000
committers-ol <s+removethis@s-ol.nu>2025-03-20 16:57:56 +0000
commit6616300ed47e5b349704e64a06b1ece036b16463 (patch)
tree7f9c5ae88d3a261736cb7713f352ea293fc7e242
parentprint effective OSC address on listen, reload messages (diff)
downloadglsl-view-6616300ed47e5b349704e64a06b1ece036b16463.tar.gz
glsl-view-6616300ed47e5b349704e64a06b1ece036b16463.zip
wrap Texture in Source
-rw-r--r--build.zig2
-rw-r--r--src/config.zig15
-rw-r--r--src/control.zig36
-rw-r--r--src/ffmpeg.zig275
-rw-r--r--src/gl.zig33
-rw-r--r--src/hap.zig (renamed from src/video/hap.zig)22
-rw-r--r--src/main.zig14
-rw-r--r--src/output.zig164
-rw-r--r--src/tsv.zig77
-rw-r--r--src/video.zig47
-rw-r--r--src/video/decoder.zig93
-rw-r--r--src/video/ffmpeg.zig126
12 files changed, 478 insertions, 426 deletions
diff --git a/build.zig b/build.zig
index fbee00c..74975b3 100644
--- a/build.zig
+++ b/build.zig
@@ -4,7 +4,7 @@ pub fn build(b: *std.Build) void {
const optimize = b.standardOptimizeOption(.{});
const target = b.standardTargetOptions(.{});
- const have_ffmpeg = b.option(bool, "textures", "enable texture/video support (needs ffmpeg)") orelse true;
+ const have_ffmpeg = b.option(bool, "ffmpeg", "enable image/video support (needs ffmpeg)") orelse true;
const have_hap = have_ffmpeg and b.option(bool, "hap", "enable HAP GPU upload support (needs snappy)") orelse true;
const have_tsv = b.option(bool, "texture-share-vk", "enable GPU image sharing (needs texture-share-vk)") orelse false;
diff --git a/src/config.zig b/src/config.zig
index a024dfa..ba46ef7 100644
--- a/src/config.zig
+++ b/src/config.zig
@@ -5,7 +5,7 @@ const build_config = @import("build_config");
pub const OutputConfig = union(enum) {
window: out.WindowOutput.Config,
- texture_share_vk: if (build_config.have_tsv) out.TSVOutput.Config else void,
+ texture_share_vk: if (build_config.have_tsv) @import("tsv.zig").TSVOutput.Config else void,
const default: OutputConfig = .{ .window = .default };
};
@@ -55,11 +55,14 @@ pub const Config = struct {
}
if (config.outputs.len == 0) {
- var output = try allocator.create(OutputConfig);
- output.* = .{ .window = .default };
- output.window.width = config.width;
- output.window.height = config.height;
- config.outputs = @as(*[1]OutputConfig, output);
+ const outputs = try allocator.alloc(OutputConfig, 1);
+ outputs[0] = .{ .window = .default };
+ outputs[0].window.width = config.width;
+ outputs[0].window.height = config.height;
+ // outputs[1] = .{ .window = .default };
+ // outputs[1].window.width = config.width;
+ // outputs[1].window.height = config.height;
+ config.outputs = outputs;
}
return config;
diff --git a/src/control.zig b/src/control.zig
index 361d079..d36ce01 100644
--- a/src/control.zig
+++ b/src/control.zig
@@ -1,8 +1,8 @@
const c = @import("c.zig");
const gl = @import("gl.zig");
-const video = @import("video.zig");
const std = @import("std");
const cfg = @import("config.zig");
+const build_config = @import("build_config");
fn verify_args(expected: u8, got: []const u8) !void {
for (got) |typ| {
@@ -16,7 +16,7 @@ fn verify_args(expected: u8, got: []const u8) !void {
fn set_one(
comptime T: type,
dest: *T,
- argv: []const[*c]c.lo_arg,
+ argv: []const [*c]c.lo_arg,
types: []const u8,
) !void {
if (types.len != 1)
@@ -49,7 +49,7 @@ fn set_one(
fn set_array(
comptime T: type,
dest: []T,
- argv: []const[*c]c.lo_arg,
+ argv: []const [*c]c.lo_arg,
types: []const u8,
) !void {
if (types.len != dest.len)
@@ -89,17 +89,17 @@ fn set_texture(
dest: *?*gl.Texture,
texture_type: gl.Texture.Type,
types: []const u8,
- argv: []const[*c]c.lo_arg,
+ argv: []const [*c]c.lo_arg,
) !void {
if (types.len != 1 or types[0] != 's') return error.invalidType;
const nameZ: [*:0]const u8 = @ptrCast(&argv[0].*.s);
const name = std.mem.span(nameZ);
- const tex = cache.textures.getPtr(name) orelse return error.texNotFound;
- if (tex.type != texture_type) return error.wrongTextureType;
+ const source = cache.textures.get(name) orelse return error.texNotFound;
+ if (source.texture.type != texture_type) return error.wrongTextureType;
- dest.* = tex;
+ dest.* = &source.texture;
}
pub const ControlServer = struct {
@@ -110,7 +110,7 @@ pub const ControlServer = struct {
progress: std.Progress.Node,
allocator: std.mem.Allocator,
- const ZigCallback = fn (*ControlServer, []const u8, []const u8, []const[*c]c.lo_arg) anyerror!bool;
+ const ZigCallback = fn (*ControlServer, []const u8, []const u8, []const [*c]c.lo_arg) anyerror!bool;
const CCallback = fn ([*c]const u8, [*c]const u8, [*c][*c]c.lo_arg, c_int, c.lo_message, ?*anyopaque) callconv(.C) c_int;
fn wrapCallback(comptime inner: ZigCallback) CCallback {
return struct {
@@ -186,7 +186,7 @@ pub const ControlServer = struct {
);
}
- fn handle_shader(self: *ControlServer, path: []const u8, types: []const u8, argv: []const[*c]c.lo_arg) !bool {
+ fn handle_shader(self: *ControlServer, path: []const u8, types: []const u8, argv: []const [*c]c.lo_arg) !bool {
_ = path;
_ = types;
@@ -197,7 +197,7 @@ pub const ControlServer = struct {
return true;
}
- fn handle_reload(self: *ControlServer, path: []const u8, types: []const u8, argv: []const[*c]c.lo_arg) !bool {
+ fn handle_reload(self: *ControlServer, path: []const u8, types: []const u8, argv: []const [*c]c.lo_arg) !bool {
_ = path;
_ = types;
_ = argv;
@@ -207,7 +207,7 @@ pub const ControlServer = struct {
return true;
}
- fn handle_uniform(self: *ControlServer, path: []const u8, types: []const u8, argv: []const[*c]c.lo_arg) !bool {
+ fn handle_uniform(self: *ControlServer, path: []const u8, types: []const u8, argv: []const [*c]c.lo_arg) !bool {
var parts = std.mem.tokenizeScalar(u8, path, '/');
_ = parts.next(); // skip /uniform
@@ -300,7 +300,10 @@ pub const ControlServer = struct {
return true;
}
- fn handle_load_texture(self: *ControlServer, path: []const u8, types: []const u8, argv: []const[*c]c.lo_arg) !bool {
+ fn handle_load_texture(self: *ControlServer, path: []const u8, types: []const u8, argv: []const [*c]c.lo_arg) !bool {
+ if (!build_config.have_ffmpeg) return error.supportDisabled;
+ const ffmpeg = @import("ffmpeg.zig");
+
_ = types;
var parts = std.mem.tokenizeScalar(u8, path, '/');
@@ -315,13 +318,13 @@ pub const ControlServer = struct {
const key = try self.cache.allocator.dupe(u8, name);
errdefer self.cache.allocator.free(key);
- const texture = try video.loadVideo(self.progress, filenameZ, texture_type);
- try self.cache.textures.put(key, texture);
+ const source = try ffmpeg.VideoSource.init(self.cache.allocator, self.progress, filenameZ, texture_type);
+ try self.cache.textures.put(key, source);
return true;
}
- fn handle_destroy_texture(self: *ControlServer, path: []const u8, types: []const u8, argv: []const[*c]c.lo_arg) !bool {
+ fn handle_destroy_texture(self: *ControlServer, path: []const u8, types: []const u8, argv: []const [*c]c.lo_arg) !bool {
_ = types;
_ = argv;
@@ -330,7 +333,8 @@ pub const ControlServer = struct {
const name = parts.next() orelse unreachable;
if (self.cache.textures.fetchRemove(name)) |kv| {
- kv.value.destroy();
+ // @TODO: find all references to kv.value.texture in UniformCache and unassign
+ kv.value.deinit(self.cache.allocator);
self.cache.allocator.free(kv.key);
} else return false;
diff --git a/src/ffmpeg.zig b/src/ffmpeg.zig
new file mode 100644
index 0000000..9d3bc9a
--- /dev/null
+++ b/src/ffmpeg.zig
@@ -0,0 +1,275 @@
+const std = @import("std");
+const c = @import("c.zig");
+const gl = @import("gl.zig");
+const build_config = @import("build_config");
+
+pub const Errors = error{
+ AVGenericError,
+ AVAllocationError,
+ HAPBadArguments,
+ HAPBufferTooSmall,
+ HAPBadFrame,
+ HAPInternalError,
+ UnsupportedCodec,
+ OutOfMemory,
+ NoFrames,
+};
+
+pub fn check(err: c_int) Errors!void {
+ if (err >= 0) return;
+ var buf: [c.AV_ERROR_MAX_STRING_SIZE]u8 = undefined;
+ _ = c.av_make_error_string(&buf, buf.len, err);
+ std.debug.print("libav error: {s}\n", .{buf});
+ return error.AVGenericError;
+}
+
+pub const Decoder = struct {
+ num_frames: i32,
+
+ process_packet_fn: *const fn (self: *Decoder, packet: *c.AVPacket, texture: ?*const gl.Texture) Errors!bool,
+ init_texture_fn: *const fn (self: *Decoder, texture: *const gl.Texture) void,
+ reset_fn: ?*const fn (self: *Decoder) void,
+ deinit_fn: *const fn (self: *Decoder, allocator: std.mem.Allocator) void,
+
+ fn processStream(
+ self: *Decoder,
+ progress: std.Progress.Node,
+ format: *c.AVFormatContext,
+ stream: *c.AVStream,
+ texture: ?*const gl.Texture,
+ ) Errors!void {
+ defer progress.end();
+
+ try check(c.avformat_seek_file(format, stream.index, 0, 0, 0, 0));
+ if (self.reset_fn) |reset_fn| reset_fn(self);
+
+ var packet = c.av_packet_alloc();
+ defer c.av_packet_free(&packet);
+
+ self.num_frames = 0;
+ var res = c.av_read_frame(format, packet);
+ while (res >= 0) : (res = c.av_read_frame(format, packet)) {
+ if (packet.*.stream_index == stream.index) {
+ const more = try self.process_packet_fn(self, packet, texture);
+ progress.setCompletedItems(@intCast(self.num_frames));
+ if (!more) break;
+ }
+ c.av_packet_unref(packet);
+ } else {
+ if (res != c.AVERROR_EOF) try check(res);
+ }
+ }
+
+ pub fn createTexture(
+ self: *Decoder,
+ outer_progress: std.Progress.Node,
+ format: *c.AVFormatContext,
+ stream: *c.AVStream,
+ texture_type: gl.Texture.Type,
+ ) Errors!gl.Texture {
+ try self.processStream(
+ outer_progress.start("scanning video frames", @intCast(stream.nb_frames)),
+ format,
+ stream,
+ null,
+ );
+ if (self.num_frames <= 0) return error.NoFrames;
+
+ const texture = gl.Texture.create(texture_type);
+ errdefer texture.destroy();
+
+ self.init_texture_fn(self, &texture);
+ try self.processStream(
+ outer_progress.start("loading video frames", @intCast(self.num_frames)),
+ format,
+ stream,
+ &texture,
+ );
+ return texture;
+ }
+
+ pub fn deinit(self: *Decoder, allocator: std.mem.Allocator) void {
+ self.deinit_fn(self, allocator);
+ }
+};
+
+pub const AVDecoder = struct {
+ decoder: Decoder,
+
+ sws_ctx: *c.SwsContext,
+ codec_par: c.AVCodecParameters,
+ codec_ctx: [*c]c.AVCodecContext,
+
+ raw_frame: [*c]c.AVFrame,
+ rgba_frame: [*c]c.AVFrame,
+
+ pub fn init(allocator: std.mem.Allocator, codec_par: c.AVCodecParameters) Errors!*Decoder {
+ const self = try allocator.create(AVDecoder);
+ errdefer allocator.destroy(self);
+
+ const codec = c.avcodec_find_decoder(codec_par.codec_id) orelse return error.UnsupportedCodec;
+
+ var codec_ctx = c.avcodec_alloc_context3(codec) orelse return error.AVAllocationError;
+ errdefer c.avcodec_free_context(&codec_ctx);
+ var raw_frame = c.av_frame_alloc() orelse return error.AVAllocationError;
+ errdefer c.av_frame_free(&raw_frame);
+ var rgba_frame = c.av_frame_alloc() orelse return error.AVAllocationError;
+ errdefer c.av_frame_free(&rgba_frame);
+
+ try check(c.avcodec_parameters_to_context(codec_ctx, &codec_par));
+ try check(c.avcodec_open2(codec_ctx, codec, null));
+ errdefer check(c.avcodec_close(codec_ctx)) catch unreachable;
+
+ const sws_ctx = c.sws_getContext(
+ codec_par.width,
+ codec_par.height,
+ codec_par.format,
+ codec_par.width,
+ codec_par.height,
+ c.AV_PIX_FMT_RGBA,
+ c.SWS_POINT,
+ null,
+ null,
+ 0,
+ ) orelse return error.AVGenericError;
+ errdefer c.sws_freeContext(sws_ctx);
+
+ self.* = .{
+ .decoder = .{
+ .process_packet_fn = processPacket,
+ .init_texture_fn = initTexture,
+ .reset_fn = reset,
+ .deinit_fn = deinit,
+ .num_frames = 0,
+ },
+ .sws_ctx = sws_ctx,
+ .codec_par = codec_par,
+ .codec_ctx = codec_ctx,
+ .raw_frame = raw_frame,
+ .rgba_frame = rgba_frame,
+ };
+ return &self.decoder;
+ }
+
+ fn deinit(decoder: *Decoder, allocator: std.mem.Allocator) void {
+ const self: *AVDecoder = @fieldParentPtr("decoder", decoder);
+
+ const raw_ptr: [*c][*c]c.AVFrame = &self.raw_frame;
+ const rgba_ptr: [*c][*c]c.AVFrame = &self.rgba_frame;
+ c.av_frame_free(raw_ptr);
+ c.av_frame_free(rgba_ptr);
+
+ c.sws_freeContext(self.sws_ctx);
+ check(c.avcodec_close(self.codec_ctx)) catch unreachable;
+ c.avcodec_free_context(&self.codec_ctx);
+
+ allocator.destroy(self);
+ }
+
+ fn reset(decoder: *Decoder) void {
+ const self: *AVDecoder = @fieldParentPtr("decoder", decoder);
+ c.avcodec_flush_buffers(self.codec_ctx);
+ }
+
+ fn initTexture(decoder: *Decoder, texture: *const gl.Texture) void {
+ const self: *AVDecoder = @fieldParentPtr("decoder", decoder);
+
+ texture.allocate(
+ self.codec_par.width,
+ self.codec_par.height,
+ self.decoder.num_frames,
+ c.GL_COMPRESSED_RGBA_S3TC_DXT1_EXT,
+ );
+ }
+
+ fn processPacket(decoder: *Decoder, packet: *c.AVPacket, texture: ?*const gl.Texture) Errors!bool {
+ const self: *AVDecoder = @fieldParentPtr("decoder", decoder);
+
+ try check(c.avcodec_send_packet(self.codec_ctx, packet));
+ while (true) {
+ const ires = c.avcodec_receive_frame(self.codec_ctx, self.raw_frame);
+ if (ires == c.AVERROR_EOF) return false;
+ if (ires == c.AVERROR(c.EAGAIN)) return true;
+ try check(ires);
+ try check(c.sws_scale_frame(self.sws_ctx, self.rgba_frame, self.raw_frame));
+
+ c.glPixelStorei(c.GL_UNPACK_ROW_LENGTH, @divFloor(self.rgba_frame.*.linesize[0], @sizeOf(u8) * 4));
+
+ if (texture) |tex| {
+ tex.setLayer(
+ self.rgba_frame.*.width,
+ self.rgba_frame.*.height,
+ self.decoder.num_frames,
+ c.GL_RGBA,
+ self.rgba_frame.*.data[0],
+ );
+
+ if (tex.type == .TEXTURE_2D) return false;
+ }
+
+ self.decoder.num_frames += 1;
+ }
+ }
+};
+
+pub const VideoSource = struct {
+ source: gl.Source,
+ decoder: *Decoder,
+
+ pub fn init(
+ allocator: std.mem.Allocator,
+ progress_root: std.Progress.Node,
+ filename: [*:0]const u8,
+ texture_type: gl.Texture.Type,
+ ) !*gl.Source {
+ const self = try allocator.create(VideoSource);
+ errdefer allocator.destroy(self);
+
+ const progress = progress_root.start("loading texture", 2);
+ defer progress.end();
+
+ var format_ctx: ?*c.AVFormatContext = null;
+ try check(c.avformat_open_input(&format_ctx, filename, null, null));
+ defer c.avformat_close_input(&format_ctx);
+
+ if (format_ctx) |format| {
+ try check(c.avformat_find_stream_info(format, null));
+
+ const video_stream = for (format.streams, 0..format.nb_streams) |c_stream, _| {
+ const stream = @as(*c.AVStream, c_stream orelse unreachable);
+ if (stream.codecpar.*.codec_type == c.AVMEDIA_TYPE_VIDEO) break stream;
+ } else unreachable;
+
+ const codec_par = video_stream.codecpar.*;
+
+ var packet = c.av_packet_alloc();
+ defer c.av_packet_free(&packet);
+
+ const is_hap = codec_par.codec_tag == c.MKTAG('H', 'a', 'p', '1') or
+ codec_par.codec_tag == c.MKTAG('H', 'a', 'p', '5');
+
+ self.* = .{
+ .source = .{
+ .texture = undefined,
+
+ .update_fn = null,
+ .deinit_fn = deinit,
+ },
+ .decoder = if (is_hap and build_config.have_hap)
+ try @import("hap.zig").HAPDecoder.init(allocator, codec_par)
+ else
+ try AVDecoder.init(allocator, codec_par),
+ };
+
+ self.source.texture = try self.decoder.createTexture(progress, format, video_stream, texture_type);
+ return &self.source;
+ } else unreachable;
+ }
+
+ fn deinit(source: *const gl.Source, allocator: std.mem.Allocator) void {
+ const self: *const VideoSource = @fieldParentPtr("source", source);
+
+ self.decoder.deinit(allocator);
+ allocator.destroy(self);
+ }
+};
diff --git a/src/gl.zig b/src/gl.zig
index 4f86f03..64a4c20 100644
--- a/src/gl.zig
+++ b/src/gl.zig
@@ -878,17 +878,37 @@ pub const ShaderProgram = struct {
}
};
+pub const Source = struct {
+ pub const Type = enum {
+ file,
+ texture_share_vk,
+ };
+
+ texture: Texture,
+
+ update_fn: ?*const fn (self: *Source) void,
+ deinit_fn: *const fn (self: *const Source, allocator: std.mem.Allocator) void,
+
+ pub fn update(self: *Source) void {
+ if (self.update_fn) |inner| inner(self);
+ }
+
+ pub fn deinit(self: *Source, allocator: std.mem.Allocator) void {
+ self.deinit_fn(self, allocator);
+ }
+};
+
pub const UniformCache = struct {
allocator: std.mem.Allocator,
uniforms: std.StringHashMap(CachedUniform),
- textures: std.StringHashMap(Texture),
+ textures: std.StringHashMap(*Source),
shader: *ShaderProgram,
pub fn init(allocator: std.mem.Allocator, shader: *ShaderProgram) UniformCache {
return UniformCache{
.allocator = allocator,
.uniforms = std.StringHashMap(CachedUniform).init(allocator),
- .textures = std.StringHashMap(Texture).init(allocator),
+ .textures = std.StringHashMap(*Source).init(allocator),
.shader = shader,
};
}
@@ -902,7 +922,7 @@ pub const UniformCache = struct {
var tit = self.textures.iterator();
while (tit.next()) |entry| {
- entry.value_ptr.destroy();
+ entry.value_ptr.*.deinit(self.allocator);
self.allocator.free(entry.key_ptr.*);
}
@@ -951,6 +971,13 @@ pub const UniformCache = struct {
}
}
}
+
+ pub fn update(self: *UniformCache) void {
+ var it = self.textures.iterator();
+ while (it.next()) |entry| {
+ entry.value_ptr.*.update();
+ }
+ }
};
pub const FramebufferObject = struct {
diff --git a/src/video/hap.zig b/src/hap.zig
index 6cc8716..696b905 100644
--- a/src/video/hap.zig
+++ b/src/hap.zig
@@ -1,9 +1,9 @@
const std = @import("std");
-const c = @import("../c.zig");
-const gl = @import("../gl.zig");
-const dec = @import("decoder.zig");
+const c = @import("c.zig");
+const gl = @import("gl.zig");
+const ffmpeg = @import("ffmpeg.zig");
-fn check(err: c_uint) dec.Errors!void {
+fn check(err: c_uint) ffmpeg.Errors!void {
return switch (err) {
c.HapResult_No_Error => return,
c.HapResult_Bad_Arguments => return error.HAPBadArguments,
@@ -15,7 +15,7 @@ fn check(err: c_uint) dec.Errors!void {
}
pub const HAPDecoder = struct {
- decoder: dec.Decoder,
+ decoder: ffmpeg.Decoder,
codec_par: c.AVCodecParameters,
format: ?c.HapTextureFormat,
@@ -24,7 +24,7 @@ pub const HAPDecoder = struct {
pub fn init(
allocator: std.mem.Allocator,
codec_par: c.AVCodecParameters,
- ) dec.Errors!*dec.Decoder {
+ ) ffmpeg.Errors!*ffmpeg.Decoder {
const self = try allocator.create(HAPDecoder);
self.* = .{
@@ -42,12 +42,12 @@ pub const HAPDecoder = struct {
return &self.decoder;
}
- pub fn deinit(decoder: *dec.Decoder, allocator: std.mem.Allocator) void {
+ fn deinit(decoder: *ffmpeg.Decoder, allocator: std.mem.Allocator) void {
const self: *HAPDecoder = @fieldParentPtr("decoder", decoder);
allocator.destroy(self);
}
- pub fn initTexture(decoder: *dec.Decoder, texture: *const gl.Texture) void {
+ fn initTexture(decoder: *ffmpeg.Decoder, texture: *const gl.Texture) void {
const self: *HAPDecoder = @fieldParentPtr("decoder", decoder);
texture.allocate(
@@ -72,11 +72,11 @@ pub const HAPDecoder = struct {
}
}
- pub fn processPacket(
- decoder: *dec.Decoder,
+ fn processPacket(
+ decoder: *ffmpeg.Decoder,
packet: *c.AVPacket,
texture: ?*const gl.Texture,
- ) dec.Errors!bool {
+ ) ffmpeg.Errors!bool {
const self: *HAPDecoder = @fieldParentPtr("decoder", decoder);
var textureCount: u32 = undefined;
diff --git a/src/main.zig b/src/main.zig
index eb7aef2..96b87eb 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -20,9 +20,10 @@ pub fn main() !void {
defer progress.end();
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
+ const cfg_allocator = arena.allocator();
defer arena.deinit();
- var config = try cfg.Config.init(arena.allocator());
+ var config = try cfg.Config.init(cfg_allocator);
_ = c.glfwSetErrorCallback(errorCallback);
@@ -60,13 +61,13 @@ pub fn main() !void {
var constants = try gl.Constants.create(window, &config);
defer constants.destroy();
- var outputs = try std.ArrayList(*out.Output).initCapacity(arena.allocator(), config.outputs.len);
+ var outputs = try std.ArrayList(*out.Output).initCapacity(cfg_allocator, config.outputs.len);
defer outputs.deinit();
for (config.outputs) |*output_config| {
- try outputs.append(out.Output.create(arena.allocator(), output_config, &constants));
+ outputs.appendAssumeCapacity(try out.Output.create(cfg_allocator, output_config, &constants));
}
defer for (outputs.items) |output| {
- output.destroy(arena.allocator());
+ output.destroy(cfg_allocator);
};
c.glfwMakeContextCurrent(window);
@@ -113,7 +114,7 @@ pub fn main() !void {
var cache = gl.UniformCache.init(std.heap.c_allocator, &main_program);
defer cache.deinit();
- const control = try ctrl.ControlServer.init(arena.allocator(), progress, &config, &cache);
+ const control = try ctrl.ControlServer.init(cfg_allocator, progress, &config, &cache);
defer control.destroy();
while (c.glfwWindowShouldClose(window) == c.GL_FALSE) {
@@ -124,6 +125,7 @@ pub fn main() !void {
prev_time = now_time;
control.update();
+ cache.update();
// const stat = try shader_file.stat();
// if (stat.mtime > last_stat.mtime or control.reload_requested) {
@@ -145,7 +147,7 @@ pub fn main() !void {
const close = output.update(fbo.texture_id);
if (close) {
const removed = outputs.swapRemove(i);
- removed.destroy(arena.allocator());
+ removed.destroy(cfg_allocator);
break;
}
}
diff --git a/src/output.zig b/src/output.zig
index 593caac..c236f4a 100644
--- a/src/output.zig
+++ b/src/output.zig
@@ -12,11 +12,14 @@ pub const Output = struct {
allocator: std.mem.Allocator,
config: *const cfg.OutputConfig,
constants: *gl.Constants,
- ) *Output {
- return switch (config.*) {
- .window => |*con| WindowOutput.create(allocator, con, constants),
- .texture_share_vk => |*con| if (build_config.have_tsv) TSVOutput.create(allocator, con, constants) else unreachable,
- };
+ ) !*Output {
+ switch (config.*) {
+ inline else => |*con| {
+ const ConfigType = @TypeOf(con.*);
+ if (ConfigType == void) return error.supportDisabled;
+ return con.create(allocator, constants);
+ },
+ }
}
pub fn update(self: *Output, texture_id: c.GLuint) bool {
@@ -28,79 +31,6 @@ pub const Output = struct {
}
};
-pub const TSVOutput = struct {
- pub const Config = struct {
- name: [:0]const u8,
-
- pub const default: Config = .{
- .name = "glsl-view",
- };
- };
-
- output: Output,
- config: *const Config,
- client: *c.GlClient,
- image_size: c.GlImageExtent,
-
- pub fn create(
- allocator: std.mem.Allocator,
- config: *const Config,
- constants: *gl.Constants,
- ) *Output {
- const self = allocator.create(TSVOutput) catch unreachable;
-
- _ = c.gl_client_initialize_external_gl() or unreachable;
-
- self.* = .{
- .output = .{
- .update_fn = update,
- .destroy_fn = destroy,
- },
- .config = config,
- .client = c.gl_client_new(c.VK_SERVER_DEFAULT_SOCKET_PATH, 1000) orelse unreachable,
- .image_size = .{
- .top_left = .{ 0, 0 },
- .bottom_right = .{ constants.config.width, constants.config.height },
- },
- };
-
- _ = c.gl_client_init_image(
- self.client,
- config.name,
- @intCast(constants.config.width),
- @intCast(constants.config.height),
- c.R8G8B8A8,
- true,
- );
-
- return &self.output;
- }
-
- fn update(output: *Output, texture_id: c.GLuint) bool {
- const self: *TSVOutput = @fieldParentPtr("output", output);
-
- var fbo: c.GLint = undefined;
- c.glGetIntegerv(c.GL_DRAW_FRAMEBUFFER_BINDING, &fbo);
-
- _ = c.gl_client_send_image(
- self.client,
- self.config.name,
- texture_id,
- c.GL_TEXTURE_2D,
- false,
- @intCast(fbo),
- &self.image_size,
- );
- return false;
- }
-
- fn destroy(output: *Output, allocator: std.mem.Allocator) void {
- const self: *TSVOutput = @fieldParentPtr("output", output);
-
- allocator.destroy(self);
- }
-};
-
pub const WindowOutput = struct {
pub const Config = struct {
const FilterMode = enum(c.GLuint) {
@@ -122,6 +52,45 @@ pub const WindowOutput = struct {
.monitor = "",
.fullscreen = false,
};
+
+ pub fn create(
+ config: *const Config,
+ allocator: std.mem.Allocator,
+ constants: *gl.Constants,
+ ) *Output {
+ const self = allocator.create(WindowOutput) catch unreachable;
+
+ c.glfwDefaultWindowHints();
+ c.glfwWindowHint(c.GLFW_FOCUSED, c.GLFW_FALSE);
+ c.glfwWindowHint(c.GLFW_FLOATING, c.GLFW_TRUE);
+ c.glfwWindowHint(c.GLFW_TRANSPARENT_FRAMEBUFFER, c.GL_TRUE);
+
+ self.* = .{
+ .constants = constants,
+ .output = .{
+ .update_fn = update,
+ .destroy_fn = destroy,
+ },
+ .window = c.glfwCreateWindow(
+ config.width,
+ config.height,
+ "glsl-view output",
+ null,
+ constants.main_window,
+ ) orelse {
+ std.debug.panic("unable to create output window\n", .{});
+ },
+ };
+
+ c.glfwSetWindowUserPointer(self.window, @as(*anyopaque, @ptrCast(self)));
+ _ = c.glfwSetKeyCallback(self.window, keyCallback);
+ _ = c.glfwSetFramebufferSizeCallback(self.window, sizeCallback);
+
+ c.glfwMakeContextCurrent(self.window);
+ constants.normalized_quad.bind(0);
+
+ return &self.output;
+ }
};
output: Output,
@@ -130,45 +99,6 @@ pub const WindowOutput = struct {
resized: bool = false,
- pub fn create(
- allocator: std.mem.Allocator,
- config: *const Config,
- constants: *gl.Constants,
- ) *Output {
- const self = allocator.create(WindowOutput) catch unreachable;
-
- c.glfwDefaultWindowHints();
- c.glfwWindowHint(c.GLFW_FOCUSED, c.GLFW_FALSE);
- c.glfwWindowHint(c.GLFW_FLOATING, c.GLFW_TRUE);
- c.glfwWindowHint(c.GLFW_TRANSPARENT_FRAMEBUFFER, c.GL_TRUE);
-
- self.* = .{
- .constants = constants,
- .output = .{
- .update_fn = update,
- .destroy_fn = destroy,
- },
- .window = c.glfwCreateWindow(
- config.width,
- config.height,
- "glsl-view output",
- null,
- constants.main_window,
- ) orelse {
- std.debug.panic("unable to create output window\n", .{});
- },
- };
-
- c.glfwSetWindowUserPointer(self.window, @as(*anyopaque, @ptrCast(self)));
- _ = c.glfwSetKeyCallback(self.window, keyCallback);
- _ = c.glfwSetFramebufferSizeCallback(self.window, sizeCallback);
-
- c.glfwMakeContextCurrent(self.window);
- constants.normalized_quad.bind(0);
-
- return &self.output;
- }
-
fn update(output: *Output, texture_id: c.GLuint) bool {
const self: *WindowOutput = @fieldParentPtr("output", output);
diff --git a/src/tsv.zig b/src/tsv.zig
new file mode 100644
index 0000000..9f15f2f
--- /dev/null
+++ b/src/tsv.zig
@@ -0,0 +1,77 @@
+const std = @import("std");
+const c = @import("c.zig");
+const gl = @import("gl.zig");
+const Output = @import("output.zig").Output;
+
+pub const TSVOutput = struct {
+ pub const Config = struct {
+ name: [:0]const u8,
+
+ pub const default: Config = .{
+ .name = "glsl-view",
+ };
+
+ pub fn create(
+ config: *const Config,
+ allocator: std.mem.Allocator,
+ constants: *gl.Constants,
+ ) *Output {
+ const self = allocator.create(TSVOutput) catch unreachable;
+
+ _ = c.gl_client_initialize_external_gl() or unreachable;
+
+ self.* = .{
+ .output = .{
+ .update_fn = update,
+ .destroy_fn = destroy,
+ },
+ .config = config,
+ .client = c.gl_client_new(c.VK_SERVER_DEFAULT_SOCKET_PATH, 1000) orelse unreachable,
+ .image_size = .{
+ .top_left = .{ 0, 0 },
+ .bottom_right = .{ constants.config.width, constants.config.height },
+ },
+ };
+
+ _ = c.gl_client_init_image(
+ self.client,
+ config.name,
+ @intCast(constants.config.width),
+ @intCast(constants.config.height),
+ c.R8G8B8A8,
+ true,
+ );
+
+ return &self.output;
+ }
+ };
+
+ output: Output,
+ config: *const Config,
+ client: *c.GlClient,
+ image_size: c.GlImageExtent,
+
+ fn update(output: *Output, texture_id: c.GLuint) bool {
+ const self: *TSVOutput = @fieldParentPtr("output", output);
+
+ var fbo: c.GLint = undefined;
+ c.glGetIntegerv(c.GL_DRAW_FRAMEBUFFER_BINDING, &fbo);
+
+ _ = c.gl_client_send_image(
+ self.client,
+ self.config.name,
+ texture_id,
+ c.GL_TEXTURE_2D,
+ false,
+ @intCast(fbo),
+ &self.image_size,
+ );
+ return false;
+ }
+
+ fn destroy(output: *Output, allocator: std.mem.Allocator) void {
+ const self: *TSVOutput = @fieldParentPtr("output", output);
+
+ allocator.destroy(self);
+ }
+};
diff --git a/src/video.zig b/src/video.zig
deleted file mode 100644
index d93a444..0000000
--- a/src/video.zig
+++ /dev/null
@@ -1,47 +0,0 @@
-const std = @import("std");
-const c = @import("c.zig");
-const gl = @import("gl.zig");
-const build_config = @import("build_config");
-const dec = @import("video/decoder.zig");
-const ffmpeg = if (build_config.have_ffmpeg) @import("video/ffmpeg.zig") else unreachable;
-const hap = if (build_config.have_hap) @import("video/hap.zig") else unreachable;
-
-const check = dec.check;
-
-pub fn loadVideo(progress_root: std.Progress.Node, filename: [*:0]const u8, texture_type: gl.Texture.Type) !gl.Texture {
- if (!build_config.have_ffmpeg) return error.texturesDisabled;
-
- const progress = progress_root.start("loading texture", 2);
- defer progress.end();
-
- var format_ctx: ?*c.AVFormatContext = null;
- try check(c.avformat_open_input(&format_ctx, filename, null, null));
- defer c.avformat_close_input(&format_ctx);
-
- if (format_ctx) |format| {
- try check(c.avformat_find_stream_info(format, null));
-
- const video_stream = for (format.streams, 0..format.nb_streams) |c_stream, _| {
- const stream = @as(*c.AVStream, c_stream orelse unreachable);
- if (stream.codecpar.*.codec_type == c.AVMEDIA_TYPE_VIDEO) break stream;
- } else unreachable;
-
- const codec_par = video_stream.codecpar.*;
-
- var packet = c.av_packet_alloc();
- defer c.av_packet_free(&packet);
-
- const allocator = std.heap.c_allocator;
-
- const is_hap = codec_par.codec_tag == c.MKTAG('H', 'a', 'p', '1') or
- codec_par.codec_tag == c.MKTAG('H', 'a', 'p', '5');
- const decoder = if (is_hap and build_config.have_hap)
- try hap.HAPDecoder.init(allocator, codec_par)
- else
- try ffmpeg.AVDecoder.init(allocator, codec_par);
-
- defer decoder.deinit(allocator);
-
- return try decoder.createTexture(progress, format, video_stream, texture_type);
- } else unreachable;
-}
diff --git a/src/video/decoder.zig b/src/video/decoder.zig
deleted file mode 100644
index 4c78374..0000000
--- a/src/video/decoder.zig
+++ /dev/null
@@ -1,93 +0,0 @@
-const std = @import("std");
-const c = @import("../c.zig");
-const gl = @import("../gl.zig");
-
-pub const Errors = error{
- AVGenericError,
- AVAllocationError,
- HAPBadArguments,
- HAPBufferTooSmall,
- HAPBadFrame,
- HAPInternalError,
- UnsupportedCodec,
- OutOfMemory,
- NoFrames,
-};
-
-pub fn check(err: c_int) Errors!void {
- if (err >= 0) return;
- var buf: [c.AV_ERROR_MAX_STRING_SIZE]u8 = undefined;
- _ = c.av_make_error_string(&buf, buf.len, err);
- std.debug.print("libav error: {s}\n", .{buf});
- return error.AVGenericError;
-}
-
-pub const Decoder = struct {
- num_frames: i32,
-
- process_packet_fn: *const fn (self: *Decoder, packet: *c.AVPacket, texture: ?*const gl.Texture) Errors!bool,
- init_texture_fn: *const fn (self: *Decoder, texture: *const gl.Texture) void,
- reset_fn: ?*const fn (self: *Decoder) void,
- deinit_fn: *const fn (self: *Decoder, allocator: std.mem.Allocator) void,
-
- fn processStream(
- self: *Decoder,
- progress: std.Progress.Node,
- format: *c.AVFormatContext,
- stream: *c.AVStream,
- texture: ?*const gl.Texture,
- ) Errors!void {
- defer progress.end();
-
- try check(c.avformat_seek_file(format, stream.index, 0, 0, 0, 0));
- if (self.reset_fn) |reset_fn| reset_fn(self);
-
- var packet = c.av_packet_alloc();
- defer c.av_packet_free(&packet);
-
- self.num_frames = 0;
- var res = c.av_read_frame(format, packet);
- while (res >= 0) : (res = c.av_read_frame(format, packet)) {
- if (packet.*.stream_index == stream.index) {
- const more = try self.process_packet_fn(self, packet, texture);
- progress.setCompletedItems(@intCast(self.num_frames));
- if (!more) break;
- }
- c.av_packet_unref(packet);
- } else {
- if (res != c.AVERROR_EOF) try check(res);
- }
- }
-
- pub fn createTexture(
- self: *Decoder,
- outer_progress: std.Progress.Node,
- format: *c.AVFormatContext,
- stream: *c.AVStream,
- texture_type: gl.Texture.Type,
- ) Errors!gl.Texture {
- try self.processStream(
- outer_progress.start("scanning video frames", @intCast(stream.nb_frames)),
- format,
- stream,
- null,
- );
- if (self.num_frames <= 0) return error.NoFrames;
-
- const texture = gl.Texture.create(texture_type);
- errdefer texture.destroy();
-
- self.init_texture_fn(self, &texture);
- try self.processStream(
- outer_progress.start("loading video frames", @intCast(self.num_frames)),
- format,
- stream,
- &texture,
- );
- return texture;
- }
-
- pub fn deinit(self: *Decoder, allocator: std.mem.Allocator) void {
- self.deinit_fn(self, allocator);
- }
-};
diff --git a/src/video/ffmpeg.zig b/src/video/ffmpeg.zig
deleted file mode 100644
index 03456d5..0000000
--- a/src/video/ffmpeg.zig
+++ /dev/null
@@ -1,126 +0,0 @@
-const std = @import("std");
-const c = @import("../c.zig");
-const gl = @import("../gl.zig");
-const build_config = @import("build_config");
-const dec = @import("decoder.zig");
-
-const check = dec.check;
-
-pub const AVDecoder = struct {
- decoder: dec.Decoder,
-
- sws_ctx: *c.SwsContext,
- codec_par: c.AVCodecParameters,
- codec_ctx: [*c]c.AVCodecContext,
-
- raw_frame: [*c]c.AVFrame,
- rgba_frame: [*c]c.AVFrame,
-
- pub fn init(allocator: std.mem.Allocator, codec_par: c.AVCodecParameters) dec.Errors!*dec.Decoder {
- const self = try allocator.create(AVDecoder);
- errdefer allocator.destroy(self);
-
- const codec = c.avcodec_find_decoder(codec_par.codec_id) orelse return error.UnsupportedCodec;
-
- var codec_ctx = c.avcodec_alloc_context3(codec) orelse return error.AVAllocationError;
- errdefer c.avcodec_free_context(&codec_ctx);
- var raw_frame = c.av_frame_alloc() orelse return error.AVAllocationError;
- errdefer c.av_frame_free(&raw_frame);
- var rgba_frame = c.av_frame_alloc() orelse return error.AVAllocationError;
- errdefer c.av_frame_free(&rgba_frame);
-
- try check(c.avcodec_parameters_to_context(codec_ctx, &codec_par));
- try check(c.avcodec_open2(codec_ctx, codec, null));
- errdefer check(c.avcodec_close(codec_ctx)) catch unreachable;
-
- const sws_ctx = c.sws_getContext(
- codec_par.width,
- codec_par.height,
- codec_par.format,
- codec_par.width,
- codec_par.height,
- c.AV_PIX_FMT_RGBA,
- c.SWS_POINT,
- null,
- null,
- 0,
- ) orelse return error.AVGenericError;
- errdefer c.sws_freeContext(sws_ctx);
-
- self.* = .{
- .decoder = .{
- .process_packet_fn = processPacket,
- .init_texture_fn = initTexture,
- .reset_fn = reset,
- .deinit_fn = deinit,
- .num_frames = 0,
- },
- .sws_ctx = sws_ctx,
- .codec_par = codec_par,
- .codec_ctx = codec_ctx,
- .raw_frame = raw_frame,
- .rgba_frame = rgba_frame,
- };
- return &self.decoder;
- }
-
- pub fn deinit(decoder: *dec.Decoder, allocator: std.mem.Allocator) void {
- const self: *AVDecoder = @fieldParentPtr("decoder", decoder);
-
- const raw_ptr: [*c][*c]c.AVFrame = &self.raw_frame;
- const rgba_ptr: [*c][*c]c.AVFrame = &self.rgba_frame;
- c.av_frame_free(raw_ptr);
- c.av_frame_free(rgba_ptr);
-
- c.sws_freeContext(self.sws_ctx);
- check(c.avcodec_close(self.codec_ctx)) catch unreachable;
- c.avcodec_free_context(&self.codec_ctx);
-
- allocator.destroy(self);
- }
-
- pub fn reset(decoder: *dec.Decoder) void {
- const self: *AVDecoder = @fieldParentPtr("decoder", decoder);
- c.avcodec_flush_buffers(self.codec_ctx);
- }
-
- pub fn initTexture(decoder: *dec.Decoder, texture: *const gl.Texture) void {
- const self: *AVDecoder = @fieldParentPtr("decoder", decoder);
-
- texture.allocate(
- self.codec_par.width,
- self.codec_par.height,
- self.decoder.num_frames,
- c.GL_COMPRESSED_RGBA_S3TC_DXT1_EXT,
- );
- }
-
- pub fn processPacket(decoder: *dec.Decoder, packet: *c.AVPacket, texture: ?*const gl.Texture) dec.Errors!bool {
- const self: *AVDecoder = @fieldParentPtr("decoder", decoder);
-
- try check(c.avcodec_send_packet(self.codec_ctx, packet));
- while (true) {
- const ires = c.avcodec_receive_frame(self.codec_ctx, self.raw_frame);
- if (ires == c.AVERROR_EOF) return false;
- if (ires == c.AVERROR(c.EAGAIN)) return true;
- try check(ires);
- try check(c.sws_scale_frame(self.sws_ctx, self.rgba_frame, self.raw_frame));
-
- c.glPixelStorei(c.GL_UNPACK_ROW_LENGTH, @divFloor(self.rgba_frame.*.linesize[0], @sizeOf(u8) * 4));
-
- if (texture) |tex| {
- tex.setLayer(
- self.rgba_frame.*.width,
- self.rgba_frame.*.height,
- self.decoder.num_frames,
- c.GL_RGBA,
- self.rgba_frame.*.data[0],
- );
-
- if (tex.type == .TEXTURE_2D) return false;
- }
-
- self.decoder.num_frames += 1;
- }
- }
-};