aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authors-ol <s+removethis@s-ol.nu>2025-07-27 21:40:42 +0000
committers-ol <s+removethis@s-ol.nu>2025-07-28 18:29:18 +0000
commit6850ad3da37ae1d49b959ddb5e8c11a2a1481bda (patch)
treeb90e231de261c62d38c009d9e6cf907838f34b8c
parentfix texture binding bug (diff)
downloadglsl-view-6850ad3da37ae1d49b959ddb5e8c11a2a1481bda.tar.gz
glsl-view-6850ad3da37ae1d49b959ddb5e8c11a2a1481bda.zip
add libav StreamSource, run Sources in threads
-rw-r--r--build.zig1
-rw-r--r--src/c.zig1
-rw-r--r--src/control.zig31
-rw-r--r--src/ffmpeg.zig163
-rw-r--r--src/gl.zig68
-rw-r--r--src/main.zig3
-rw-r--r--src/tsv.zig23
7 files changed, 237 insertions, 53 deletions
diff --git a/build.zig b/build.zig
index 74975b3..f506153 100644
--- a/build.zig
+++ b/build.zig
@@ -29,6 +29,7 @@ pub fn build(b: *std.Build) void {
if (have_ffmpeg) {
exe.linkSystemLibrary("libavcodec");
exe.linkSystemLibrary("libavformat");
+ exe.linkSystemLibrary("libavdevice");
exe.linkSystemLibrary("libavutil");
exe.linkSystemLibrary("libswscale");
}
diff --git a/src/c.zig b/src/c.zig
index ca8e029..1618030 100644
--- a/src/c.zig
+++ b/src/c.zig
@@ -8,6 +8,7 @@ pub usingnamespace @cImport({
if (build_config.have_ffmpeg) {
@cInclude("libavcodec/avcodec.h");
@cInclude("libavformat/avformat.h");
+ @cInclude("libavdevice/avdevice.h");
@cInclude("libswscale/swscale.h");
}
diff --git a/src/control.zig b/src/control.zig
index ed2531e..48aabc3 100644
--- a/src/control.zig
+++ b/src/control.zig
@@ -107,6 +107,7 @@ pub const ControlServer = struct {
cache: *gl.UniformCache,
config: *cfg.Config,
+ constants: *const gl.Constants,
progress: std.Progress.Node,
allocator: std.mem.Allocator,
@@ -141,8 +142,9 @@ pub const ControlServer = struct {
pub fn init(
allocator: std.mem.Allocator,
progress: std.Progress.Node,
- config: *cfg.Config,
cache: *gl.UniformCache,
+ config: *cfg.Config,
+ constants: *const gl.Constants,
) !*ControlServer {
var self: *ControlServer = try allocator.create(ControlServer);
self.* = .{
@@ -150,6 +152,7 @@ pub const ControlServer = struct {
.cache = cache,
.config = config,
+ .constants = constants,
.progress = progress,
.allocator = allocator,
};
@@ -167,6 +170,7 @@ pub const ControlServer = struct {
if (build_config.have_ffmpeg) {
_ = c.lo_server_add_method(self.server, "/texture/*/video", "ss", wrapCallback(handle_texture_video), @as(*anyopaque, @ptrCast(self)));
+ _ = c.lo_server_add_method(self.server, "/texture/*/stream", "ss", wrapCallback(handle_texture_stream), @as(*anyopaque, @ptrCast(self)));
}
if (build_config.have_tsv) {
_ = c.lo_server_add_method(self.server, "/texture/*/tsv", "ss", wrapCallback(handle_texture_tsv), @as(*anyopaque, @ptrCast(self)));
@@ -327,6 +331,29 @@ pub const ControlServer = struct {
return true;
}
+ fn handle_texture_stream(self: *ControlServer, path: []const u8, types: []const u8, argv: []const [*c]c.lo_arg) !bool {
+ _ = types;
+
+ var parts = std.mem.tokenizeScalar(u8, path, '/');
+ _ = parts.next();
+ const name = parts.next() orelse unreachable;
+ if (self.cache.textures.contains(name)) return error.textureNameTaken;
+
+ const texture_typeZ: [*:0]const u8 = @ptrCast(&argv[0].*.s);
+ const filenameZ: [*:0]const u8 = @ptrCast(&argv[1].*.s);
+
+ if (!std.mem.eql(u8, std.mem.span(texture_typeZ), "TEXTURE_2D")) return error.textureTypeInvalid;
+
+ const ffmpeg = @import("ffmpeg.zig");
+ const source = try ffmpeg.StreamSource.init(self.cache.allocator, self.constants, filenameZ);
+
+ const key = try self.cache.allocator.dupe(u8, name);
+ errdefer self.cache.allocator.free(key);
+ try self.cache.textures.put(key, source);
+
+ return true;
+ }
+
fn handle_texture_tsv(self: *ControlServer, path: []const u8, types: []const u8, argv: []const [*c]c.lo_arg) !bool {
_ = types;
@@ -340,7 +367,7 @@ pub const ControlServer = struct {
const tsv_nameZ: [*:0]const u8 = @ptrCast(&argv[1].*.s);
const tsv = @import("tsv.zig");
- const source = try tsv.TSVSource.init(self.cache.allocator, tsv_nameZ, texture_type);
+ const source = try tsv.TSVSource.init(self.cache.allocator, self.constants, tsv_nameZ, texture_type);
const key = try self.cache.allocator.dupe(u8, name);
errdefer self.cache.allocator.free(key);
diff --git a/src/ffmpeg.zig b/src/ffmpeg.zig
index f848ef8..be7b6bf 100644
--- a/src/ffmpeg.zig
+++ b/src/ffmpeg.zig
@@ -23,6 +23,23 @@ pub fn check(err: c_int) Errors!void {
return error.AVGenericError;
}
+fn get_format_context(filename: [*:0]const u8, format_name: ?[*:0]const u8) !*c.AVFormatContext {
+ var format_ctx: ?*c.AVFormatContext = null;
+ var format: ?*const c.AVInputFormat = null;
+ var options: ?*c.AVDictionary = null;
+
+ c.avdevice_register_all();
+
+ if (format_name) |name| {
+ format = c.av_find_input_format(name);
+ }
+
+ try check(c.avformat_open_input(&format_ctx, filename, format, &options));
+ errdefer c.avformat_close_input(&format_ctx);
+
+ return format_ctx orelse error.AVGenericError;
+}
+
pub const Decoder = struct {
num_frames: i32,
@@ -231,42 +248,38 @@ pub const VideoSource = struct {
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);
+ var format = try get_format_context(filename, null);
+ defer c.avformat_close_input(@ptrCast(&format));
- if (format_ctx) |format| {
- try check(c.avformat_find_stream_info(format, null));
+ 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 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.*;
+ const codec_par = video_stream.codecpar.*;
- var packet = c.av_packet_alloc();
- defer c.av_packet_free(&packet);
+ 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');
+ 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,
+ 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),
- };
+ .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;
+ self.source.texture = try self.decoder.createTexture(progress, format, video_stream, texture_type);
+ return &self.source;
}
fn deinit(source: *const gl.Source, allocator: std.mem.Allocator) void {
@@ -276,3 +289,97 @@ pub const VideoSource = struct {
allocator.destroy(self);
}
};
+
+pub const StreamSource = struct {
+ source: gl.Source,
+
+ decoder: *Decoder,
+ format: *c.AVFormatContext,
+ stream: i32,
+ packet: *c.AVPacket,
+
+ thread: *gl.Thread,
+
+ pub fn init(
+ allocator: std.mem.Allocator,
+ constants: *const gl.Constants,
+ filename: [*:0]const u8,
+ ) !*gl.Source {
+ const self = try allocator.create(StreamSource);
+ errdefer allocator.destroy(self);
+
+ var format = try get_format_context(filename, null);
+ errdefer c.avformat_close_input(@ptrCast(&format));
+ format.flags |= c.AVFMT_FLAG_NONBLOCK;
+
+ 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();
+ errdefer c.av_packet_free(&packet);
+
+ const texture = gl.Texture.create(.TEXTURE_2D);
+ texture.allocate(
+ codec_par.width,
+ codec_par.height,
+ 1,
+ c.GL_RGBA8,
+ );
+
+ const decoder = try AVDecoder.init(allocator, codec_par);
+
+ self.* = .{
+ .source = .{
+ .texture = texture,
+
+ .deinit_fn = deinit,
+ },
+
+ .decoder = decoder,
+ .format = format,
+ .stream = video_stream.index,
+ .packet = packet,
+
+ .thread = undefined,
+ };
+
+ try self.update();
+ self.thread = try gl.Thread.init(allocator, constants, update_loop, .{self});
+
+ return &self.source;
+ }
+
+ fn update_loop(self: *StreamSource) !void {
+ while (!self.thread.quit) {
+ try self.update();
+ c.glFlush();
+ try std.Thread.yield();
+ }
+ }
+
+ fn update(self: *StreamSource) !void {
+ try check(c.av_read_frame(self.format, self.packet));
+ defer c.av_packet_unref(self.packet);
+
+ if (self.packet.*.stream_index != self.stream) return;
+
+ self.decoder.num_frames = 0;
+ _ = try self.decoder.process_packet_fn(self.decoder, self.packet, &self.source.texture);
+ }
+
+ fn deinit(source: *gl.Source, allocator: std.mem.Allocator) void {
+ const self: *StreamSource = @fieldParentPtr("source", source);
+
+ self.thread.deinit(allocator);
+ self.decoder.deinit(allocator);
+ c.av_packet_free(@ptrCast(&self.packet));
+ c.avformat_close_input(@ptrCast(&self.format));
+ allocator.destroy(self);
+ }
+};
diff --git a/src/gl.zig b/src/gl.zig
index 3eaa120..2b7e9ea 100644
--- a/src/gl.zig
+++ b/src/gl.zig
@@ -879,6 +879,61 @@ pub const ShaderProgram = struct {
}
};
+pub const Thread = struct {
+ thread: std.Thread,
+ window: *c.GLFWwindow,
+ quit: bool,
+
+ pub fn init(
+ allocator: std.mem.Allocator,
+ constants: *const Constants,
+ comptime f: anytype,
+ args: anytype,
+ ) !*Thread {
+ const thread = try allocator.create(Thread);
+ errdefer allocator.destroy(thread);
+
+ thread.* = .{
+ .thread = undefined,
+ .window = undefined,
+ .quit = false,
+ };
+
+ thread.thread = try std.Thread.spawn(
+ .{},
+ Thread.runner(f),
+ .{ thread, constants, args },
+ );
+
+ return thread;
+ }
+
+ pub const InitFn = fn (*Thread, *const Constants, anytype) anyerror!void;
+
+ fn runner(comptime f: anytype) InitFn {
+ const ReturnType = @typeInfo(@TypeOf(f)).@"fn".return_type.?;
+ const Impl = struct {
+ pub fn init(self: *Thread, constants: *const Constants, args: anytype) ReturnType {
+ c.glfwWindowHint(c.GLFW_VISIBLE, c.GLFW_FALSE);
+ self.window = c.glfwCreateWindow(200, 200, "glsl-view Thread", null, constants.main_window) orelse unreachable;
+ c.glfwMakeContextCurrent(self.window);
+
+ return @call(.auto, f, args);
+ }
+ };
+
+ return Impl.init;
+ }
+
+ pub fn deinit(self: *Thread, allocator: std.mem.Allocator) void {
+ self.quit = true;
+ self.thread.join();
+
+ c.glfwDestroyWindow(self.window);
+ allocator.destroy(self);
+ }
+};
+
pub const Source = struct {
pub const Type = enum {
file,
@@ -886,14 +941,8 @@ pub const Source = struct {
};
texture: Texture,
-
- update_fn: ?*const fn (self: *Source) void,
deinit_fn: *const fn (self: *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);
}
@@ -972,13 +1021,6 @@ 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/main.zig b/src/main.zig
index 0883c15..1933e11 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -114,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(cfg_allocator, progress, &config, &cache);
+ const control = try ctrl.ControlServer.init(cfg_allocator, progress, &cache, &config, &constants);
defer control.destroy();
while (c.glfwWindowShouldClose(window) == c.GL_FALSE) {
@@ -124,7 +124,6 @@ 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) {
diff --git a/src/tsv.zig b/src/tsv.zig
index 33c8c92..77c45ae 100644
--- a/src/tsv.zig
+++ b/src/tsv.zig
@@ -30,9 +30,11 @@ fn get_or_init_client() *c.GlClient {
pub const TSVSource = struct {
source: gl.Source,
name: [:0]const u8,
+ thread: *gl.Thread,
pub fn init(
allocator: std.mem.Allocator,
+ constants: *const gl.Constants,
name: [*:0]const u8,
texture_type: gl.Texture.Type,
) !*gl.Source {
@@ -43,10 +45,10 @@ pub const TSVSource = struct {
.source = .{
.texture = gl.Texture.create(texture_type),
- .update_fn = update,
.deinit_fn = deinit,
},
.name = try allocator.dupeZ(u8, std.mem.span(name)),
+ .thread = undefined,
};
errdefer allocator.free(self.name);
@@ -61,7 +63,8 @@ pub const TSVSource = struct {
c.GL_RGBA8,
);
- self.source.update();
+ self.update();
+ self.thread = try gl.Thread.init(allocator, constants, update_loop, .{self});
return &self.source;
}
@@ -69,23 +72,27 @@ pub const TSVSource = struct {
fn deinit(source: *const gl.Source, allocator: std.mem.Allocator) void {
const self: *const TSVSource = @fieldParentPtr("source", source);
+ self.thread.deinit(allocator);
allocator.free(self.name);
allocator.destroy(self);
}
- fn update(source: *const gl.Source) void {
- const self: *const TSVSource = @fieldParentPtr("source", source);
-
- var fbo: c.GLint = undefined;
- c.glGetIntegerv(c.GL_DRAW_FRAMEBUFFER_BINDING, &fbo);
+ fn update_loop(self: *TSVSource) !void {
+ while (!self.thread.quit) {
+ self.update();
+ c.glFlush();
+ try std.Thread.yield();
+ }
+ }
+ fn update(self: *TSVSource) void {
_ = c.gl_client_recv_image(
get_or_init_client(),
self.name.ptr,
self.source.texture.id,
@intFromEnum(self.source.texture.type),
false,
- @intCast(fbo),
+ 0,
null,
);
}