diff options
| author | s-ol <s+removethis@s-ol.nu> | 2025-03-20 13:39:03 +0000 |
|---|---|---|
| committer | s-ol <s+removethis@s-ol.nu> | 2025-03-20 16:57:56 +0000 |
| commit | 6616300ed47e5b349704e64a06b1ece036b16463 (patch) | |
| tree | 7f9c5ae88d3a261736cb7713f352ea293fc7e242 /src | |
| parent | print effective OSC address on listen, reload messages (diff) | |
| download | glsl-view-6616300ed47e5b349704e64a06b1ece036b16463.tar.gz glsl-view-6616300ed47e5b349704e64a06b1ece036b16463.zip | |
wrap Texture in Source
Diffstat (limited to 'src')
| -rw-r--r-- | src/config.zig | 15 | ||||
| -rw-r--r-- | src/control.zig | 36 | ||||
| -rw-r--r-- | src/ffmpeg.zig | 275 | ||||
| -rw-r--r-- | src/gl.zig | 33 | ||||
| -rw-r--r-- | src/hap.zig (renamed from src/video/hap.zig) | 22 | ||||
| -rw-r--r-- | src/main.zig | 14 | ||||
| -rw-r--r-- | src/output.zig | 164 | ||||
| -rw-r--r-- | src/tsv.zig | 77 | ||||
| -rw-r--r-- | src/video.zig | 47 | ||||
| -rw-r--r-- | src/video/decoder.zig | 93 | ||||
| -rw-r--r-- | src/video/ffmpeg.zig | 126 |
11 files changed, 477 insertions, 425 deletions
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); + } +}; @@ -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; - } - } -}; |
