diff options
| author | s-ol <s+removethis@s-ol.nu> | 2025-07-27 21:40:42 +0000 |
|---|---|---|
| committer | s-ol <s+removethis@s-ol.nu> | 2025-07-28 18:29:18 +0000 |
| commit | 6850ad3da37ae1d49b959ddb5e8c11a2a1481bda (patch) | |
| tree | b90e231de261c62d38c009d9e6cf907838f34b8c | |
| parent | fix texture binding bug (diff) | |
| download | glsl-view-6850ad3da37ae1d49b959ddb5e8c11a2a1481bda.tar.gz glsl-view-6850ad3da37ae1d49b959ddb5e8c11a2a1481bda.zip | |
add libav StreamSource, run Sources in threads
| -rw-r--r-- | build.zig | 1 | ||||
| -rw-r--r-- | src/c.zig | 1 | ||||
| -rw-r--r-- | src/control.zig | 31 | ||||
| -rw-r--r-- | src/ffmpeg.zig | 163 | ||||
| -rw-r--r-- | src/gl.zig | 68 | ||||
| -rw-r--r-- | src/main.zig | 3 | ||||
| -rw-r--r-- | src/tsv.zig | 23 |
7 files changed, 237 insertions, 53 deletions
@@ -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"); } @@ -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); + } +}; @@ -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, ); } |
