31#include <condition_variable>
46#include <opencv2/opencv.hpp>
55#include <unordered_map>
64#include <rtmidi/RtMidi.h>
72#include <webp/encode.h>
78#include <ac-gpu/ac-gpu.hpp>
79#include <cuda_gl_interop.h>
83#define CHECK_CUDA(call) \
85 static_cast<void>(call); \
105#include <libavcodec/avcodec.h>
106#include <libavformat/avformat.h>
107#include <libavutil/hwcontext.h>
108#include <libavutil/imgutils.h>
109#include <libswscale/swscale.h>
112#include <linux/videodev2.h>
113#include <semaphore.h>
114#include <sys/ioctl.h>
119#if defined(__APPLE__)
121#include <mach-o/dyld.h>
122#include <semaphore.h>
128#include <glm/gtc/matrix_transform.hpp>
129#include <glm/gtc/type_ptr.hpp>
131#ifdef ACMX2_WITH_CUDA
132#include <opencv2/core/cuda.hpp>
133#include <opencv2/cudaimgproc.hpp>
134#include <opencv2/cudawarping.hpp>
136#include <opencv2/opencv.hpp>
137#include <string_view>
154#if defined(__linux__)
155 std::vector<char> executable_path(1024,
'\0');
157 const ssize_t
length = readlink(
"/proc/self/exe", executable_path.data(), executable_path.size() - 1);
161 if (
static_cast<size_t>(
length) < executable_path.size() - 1) {
162 executable_path[
static_cast<size_t>(
length)] =
'\0';
163 return std::filesystem::path(executable_path.data()).parent_path();
165 executable_path.resize(executable_path.size() * 2,
'\0');
167#elif defined(__APPLE__)
168 uint32_t required_size = 0;
169 if (_NSGetExecutablePath(
nullptr, &required_size) != -1 || required_size == 0) {
173 std::vector<char> executable_path(required_size,
'\0');
174 if (_NSGetExecutablePath(executable_path.data(), &required_size) != 0) {
178 std::error_code error;
179 const std::filesystem::path resolved_path = std::filesystem::weakly_canonical(executable_path.data(), error);
180 return error ? std::filesystem::path(executable_path.data()).parent_path() : resolved_path.parent_path();
182 std::vector<wchar_t> executable_path(MAX_PATH);
184 const DWORD
length = GetModuleFileNameW(
nullptr, executable_path.data(),
static_cast<DWORD
>(executable_path.size()));
188 if (
length < executable_path.size() - 1U) {
189 return std::filesystem::path(std::wstring(executable_path.data(),
length)).parent_path();
191 if (executable_path.size() >= 32768U) {
194 executable_path.resize(executable_path.size() * 2U);
202 if (std::getenv(name) !=
nullptr) {
206 return _putenv_s(name, value) == 0;
208 return setenv(name, value, 0) == 0;
215 if (executable_dir.empty()) {
219 const std::vector<std::filesystem::path> candidates = {
220 executable_dir.parent_path() /
"share" /
"acmx2",
221#if defined(__APPLE__)
222 executable_dir.parent_path() /
"Resources" /
"acmx2",
226 for (
const std::filesystem::path &candidate : candidates) {
227 std::error_code error;
228 if (std::filesystem::is_regular_file(candidate /
"data" /
"win-icon.png", error)) {
239 const GLubyte *value = glGetString(name);
241 return "unavailable";
243 return reinterpret_cast<const char *
>(value);
254#if defined(__linux__) || defined(_WIN32)
263static bool probe_open_gl_context(
int major,
int minor, std::string &error_message) {
264 const bool video_was_initialized = (SDL_WasInit(SDL_INIT_VIDEO) & SDL_INIT_VIDEO) != 0;
265 if (!video_was_initialized && SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) {
266 error_message = SDL_GetError();
270 SDL_GL_ResetAttributes();
271 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, major);
272 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, minor);
273 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
274 SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
275 SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
277 SDL_Window *probe_window = SDL_CreateWindow(
"ACMX2 OpenGL Probe", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 32, 32, SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN);
279 error_message = SDL_GetError();
280 if (!video_was_initialized) {
281 SDL_QuitSubSystem(SDL_INIT_VIDEO);
286 SDL_GLContext probe_context = SDL_GL_CreateContext(probe_window);
287 bool version_supported =
false;
289 int actual_major = 0;
290 int actual_minor = 0;
291 SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &actual_major);
292 SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &actual_minor);
293 version_supported = actual_major > major || (actual_major == major && actual_minor >= minor);
294 if (!version_supported) {
295 error_message =
"driver returned OpenGL " + std::to_string(actual_major) +
"." + std::to_string(actual_minor);
297 SDL_GL_DeleteContext(probe_context);
299 error_message = SDL_GetError();
302 SDL_DestroyWindow(probe_window);
303 if (!video_was_initialized) {
304 SDL_QuitSubSystem(SDL_INIT_VIDEO);
306 return version_supported;
311 std::string error_message;
312 if (probe_open_gl_context(4, 3, error_message)) {
313 mx::system_out <<
"acmx2: OpenGL 4.3 context available; compute shaders enabled\n";
317 mx::system_out <<
"acmx2: OpenGL 4.3 unavailable (" << error_message <<
"); falling back to OpenGL 4.1 with compute shaders disabled\n";
318 error_message.clear();
319 if (!probe_open_gl_context(4, 1, error_message)) {
320 throw std::runtime_error(
"OpenGL 4.1 or newer is required: " + error_message);
330#if defined(__linux__) || defined(_WIN32)
333 glGetIntegerv(GL_MAJOR_VERSION, &major);
334 glGetIntegerv(GL_MINOR_VERSION, &minor);
350 GLint max_vertex_components = 0;
351 GLint max_fragment_components = 0;
352 GLint max_uniform_locations = 0;
354 glGetIntegerv(GL_MAX_VERTEX_UNIFORM_COMPONENTS, &max_vertex_components);
355 glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, &max_fragment_components);
357 GLint major_version = 0;
358 GLint minor_version = 0;
359 glGetIntegerv(GL_MAJOR_VERSION, &major_version);
360 glGetIntegerv(GL_MINOR_VERSION, &minor_version);
361 bool uniform_locations_supported = major_version > 4 || (major_version == 4 && minor_version >= 3);
362#ifdef GL_ARB_explicit_uniform_location
363 uniform_locations_supported = uniform_locations_supported || SDL_GL_ExtensionSupported(
"GL_ARB_explicit_uniform_location") == SDL_TRUE;
366#ifdef GL_MAX_UNIFORM_LOCATIONS
367 if (uniform_locations_supported) {
368 glGetIntegerv(GL_MAX_UNIFORM_LOCATIONS, &max_uniform_locations);
371 uniform_locations_supported =
false;
374 mx::system_out <<
"acmx2: OpenGL uniform limits: vertex components=" << max_vertex_components <<
" (GL_MAX_VERTEX_UNIFORM_COMPONENTS), "
375 <<
"fragment components=" << max_fragment_components <<
" (GL_MAX_FRAGMENT_UNIFORM_COMPONENTS), uniform locations=";
376 if (uniform_locations_supported) {
377 mx::system_out << max_uniform_locations;
379 mx::system_out <<
"unsupported by this OpenGL context";
381 mx::system_out <<
" (GL_MAX_UNIFORM_LOCATIONS)\n";
393static bool isV4l2LoopbackDevice(
int device_index) {
394 const std::string device_path =
"/dev/video" + std::to_string(device_index);
395 const int fd = open(device_path.c_str(), O_RDWR | O_NONBLOCK);
400 v4l2_capability capability{};
401 const bool queried = ioctl(fd, VIDIOC_QUERYCAP, &capability) == 0;
407 const std::string driver(
reinterpret_cast<const char *
>(capability.driver));
408 return driver.find(
"v4l2loopback") != std::string::npos || driver.find(
"v4l2 loopback") != std::string::npos;
413 const auto first = raw.find_first_not_of(
" \t\r\n");
414 if (first == std::string::npos) {
417 const auto last = raw.find_last_not_of(
" \t\r\n");
418 std::string entry = raw.substr(first, last - first + 1);
424 std::replace(entry.begin(), entry.end(),
'\\',
'/');
425 std::filesystem::path rel(entry);
426 if (rel.is_absolute()) {
430 std::filesystem::path normalized = rel.lexically_normal();
431 std::string normalized_str = normalized.generic_string();
432 while (normalized_str.rfind(
"./", 0) == 0) {
433 normalized_str.erase(0, 2);
436 if (normalized_str.empty() || normalized_str ==
"." || normalized_str ==
"..") {
439 if (normalized_str.rfind(
"../", 0) == 0 || normalized_str.find(
"/../") != std::string::npos || (normalized_str.size() >= 3 && normalized_str.compare(normalized_str.size() - 3, 3,
"/..") == 0)) {
443 return normalized_str;
448 std::filesystem::path base = std::filesystem::weakly_canonical(std::filesystem::path(library_path), ec);
451 base = std::filesystem::absolute(std::filesystem::path(library_path), ec);
457 std::filesystem::path target = std::filesystem::weakly_canonical(base / std::filesystem::path(relative_path), ec);
462 const std::filesystem::path relative = target.lexically_relative(base);
463 const std::string relative_str = relative.generic_string();
464 if (relative.empty() || relative_str ==
".." || relative_str.rfind(
"../", 0) == 0) {
468 if (!std::filesystem::exists(target) || !std::filesystem::is_regular_file(target)) {
472 resolved_full_path = target.string();
476static bool isEditorPreviewPath(
const std::filesystem::path &library_path,
const std::filesystem::path &requested_path) {
477 std::error_code error;
478 const std::filesystem::path preview_root = std::filesystem::weakly_canonical(library_path /
".acmx2-editor-preview", error);
481 const std::filesystem::path relative = requested_path.lexically_relative(preview_root);
482 const std::string relative_text = relative.generic_string();
483 return !relative.empty() && relative_text !=
".." && relative_text.rfind(
"../", 0) != 0;
487 if (requested_path.parent_path().filename() !=
".acmx2-editor-preview")
489 const std::filesystem::path source_root = requested_path.parent_path().parent_path();
490 const std::filesystem::path relative = loaded_shader.lexically_relative(source_root);
491 const std::string relative_text = relative.generic_string();
492 return !relative.empty() && relative_text !=
".." && relative_text.rfind(
"../", 0) != 0;
500 std::string extension = std::filesystem::path(path).extension().string();
501 std::transform(extension.begin(), extension.end(), extension.begin(), [](
unsigned char ch) { return static_cast<char>(std::tolower(ch)); });
502 return extension ==
".comp";
523 return std::all_of(name.begin() + 1, name.end(), [](
unsigned char ch) { return std::isalnum(ch) || ch ==
'_'; });
527 const std::filesystem::path root(library_path);
528 const std::filesystem::path json_path = root /
"library.json";
529 if (std::filesystem::is_regular_file(json_path))
530 return json_path.string();
531 const std::filesystem::path text_path = root /
"index.txt";
532 if (std::filesystem::is_regular_file(text_path))
533 return text_path.string();
541 if (manifest.
path.empty()) {
542 error =
"No library.json or index.txt found at: " + library_path;
546 if (std::filesystem::path(manifest.
path).extension() ==
".json") {
549 cv::FileStorage storage(manifest.
path, cv::FileStorage::READ | cv::FileStorage::FORMAT_JSON);
550 if (!storage.isOpened()) {
551 error =
"Could not open shader manifest: " + manifest.
path;
554 const cv::FileNode shaders = storage[
"shaders"];
555 if (shaders.type() == cv::FileNode::NONE || !shaders.isSeq()) {
556 error = manifest.
path +
" must contain a 'shaders' array";
559 for (
const cv::FileNode &entry : shaders) {
561 if (entry.isString()) {
563 }
else if (entry.isMap() && !entry[
"file"].empty()) {
564 entry[
"file"] >> file;
566 error = manifest.
path +
" contains a shader entry without a file name";
569 manifest.
entries.push_back(std::move(file));
571 const cv::FileNode uniforms = storage[
"custom_uniforms"];
572 if (!uniforms.empty()) {
573 if (!uniforms.isMap()) {
574 error = manifest.
path +
" field 'custom_uniforms' must be an object";
577 for (
auto it = uniforms.begin(); it != uniforms.end(); ++it) {
579 error = manifest.
path +
" contains too many custom uniforms";
582 const cv::FileNode entry = *it;
584 uniform.
name = entry.name();
586 error = manifest.
path +
" contains an invalid custom uniform: " + uniform.
name;
589 if (!entry[
"minimum"].empty())
590 entry[
"minimum"] >> uniform.
minimum;
591 if (!entry[
"maximum"].empty())
592 entry[
"maximum"] >> uniform.
maximum;
593 if (!entry[
"step"].empty())
594 entry[
"step"] >> uniform.
step;
596 if (!entry[
"value"].empty())
597 entry[
"value"] >> uniform.
value;
599 error = manifest.
path +
" contains an invalid range for custom uniform: " + uniform.
name;
607 }
catch (
const cv::Exception &e) {
608 error =
"Could not parse " + manifest.
path +
": " + e.what();
614 std::ifstream file(manifest.
path);
615 if (!file.is_open()) {
616 error =
"Could not open shader manifest: " + manifest.
path;
620 while (std::getline(file, line))
621 manifest.
entries.push_back(std::move(line));
625static bool writeJsonShaderManifest(
const std::string &path,
const std::vector<std::string> &entries,
const std::vector<ShaderManifestData::CustomUniform> &custom_uniforms, std::string &error) {
627 cv::FileStorage storage(path, cv::FileStorage::WRITE | cv::FileStorage::FORMAT_JSON);
628 if (!storage.isOpened()) {
629 error =
"Could not write shader manifest: " + path;
632 storage <<
"version" << 1;
635 for (
const std::string &entry : entries)
638 if (!custom_uniforms.empty()) {
639 storage <<
"custom_uniforms"
641 for (
const auto &uniform : custom_uniforms) {
642 storage << uniform.name <<
"{"
643 <<
"minimum" << uniform.minimum <<
"maximum" << uniform.maximum <<
"step" << uniform.step <<
"value" << uniform.value <<
"}";
649 }
catch (
const cv::Exception &e) {
650 error =
"Could not write " + path +
": " + e.what();
659 shader_files.clear();
660 for (
const std::string &entry : manifest.
entries) {
664 std::string full_path;
666 shader_files.push_back(*shader_entry);
668 std::sort(shader_files.begin(), shader_files.end(), [](
const std::string &a,
const std::string &b) { return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end(), [](unsigned char ca, unsigned char cb) { return std::tolower(ca) < std::tolower(cb); }); });
673 std::vector<std::string> shader_files;
676 mx::system_err <<
"acmx2: " << error <<
"\n";
680static int shaderIndexForFile(
const std::vector<std::string> &shader_files,
const std::string &shader_file) {
681 const auto selected = std::find(shader_files.begin(), shader_files.end(), shader_file);
682 if (selected == shader_files.end())
684 return static_cast<int>(std::distance(shader_files.begin(), selected));
700#if defined(__linux__) || defined(__APPLE__)
701 extern "C" void acmx2_signal_handler(
int ) {
707 struct sigaction sa{};
708 sa.sa_handler = &acmx2_signal_handler;
709 sigemptyset(&sa.sa_mask);
713 sigaction(SIGINT, &sa,
nullptr);
714 sigaction(SIGTERM, &sa,
nullptr);
715 sigaction(SIGHUP, &sa,
nullptr);
718 extern "C" void acmx2_signal_handler(
int) {
g_shutdown_requested.store(
true, std::memory_order_relaxed); }
721 std::signal(SIGINT, &acmx2_signal_handler);
722 std::signal(SIGTERM, &acmx2_signal_handler);
729#if defined(__APPLE__)
730class ScopedStderrSilence {
732 ScopedStderrSilence() {
733 saved_stderr_fd = dup(STDERR_FILENO);
734 if (saved_stderr_fd < 0) {
737 null_fd = open(
"/dev/null", O_WRONLY);
739 close(saved_stderr_fd);
740 saved_stderr_fd = -1;
743 if (dup2(null_fd, STDERR_FILENO) < 0) {
745 close(saved_stderr_fd);
747 saved_stderr_fd = -1;
753 ~ScopedStderrSilence() {
754 if (saved_stderr_fd >= 0) {
755 dup2(saved_stderr_fd, STDERR_FILENO);
756 close(saved_stderr_fd);
763 ScopedStderrSilence(
const ScopedStderrSilence &) =
delete;
764 ScopedStderrSilence &operator=(
const ScopedStderrSilence &) =
delete;
767 int saved_stderr_fd = -1;
794 if (!src || !src->data[0] || !src->data[1]) {
797 const int w = src->width;
798 const int h = src->height;
799 if (w <= 0 || h <= 0) {
803 const bool is_p010 = (src->format == AV_PIX_FMT_P010LE);
804 if (!is_p010 && !src->data[2]) {
811 int sample_shift = 0;
816 const int y_stride_b = src->linesize[0];
817 const int uv_stride_b = src->linesize[1];
818 const int v_stride_b = is_p010 ? 0 : src->linesize[2];
821 constexpr float kCrR = 1.4746f;
822 constexpr float kCbG = -0.16455312684366f;
823 constexpr float kCrG = -0.57135313725490f;
824 constexpr float kCbB = 1.8814f;
826 out.create(h, w, CV_16UC4);
828 auto sample10 = [&](
const uint8_t *plane,
int stride_b,
int x,
int y) ->
int {
829 const uint16_t raw = *
reinterpret_cast<const uint16_t *
>(plane + y * stride_b + x * 2);
830 return static_cast<int>(raw >> sample_shift);
833 const uint8_t *yp = src->data[0];
834 const uint8_t *up = src->data[1];
835 const uint8_t *vp = is_p010 ? nullptr : src->data[2];
841 constexpr float kInvY = 1.0f / 876.0f;
842 constexpr float kInvC = 1.0f / 896.0f;
844 for (
int y = 0; y < h; ++y) {
845 const int cy = y >> 1;
846 uint16_t *dst = out.ptr<uint16_t>(y);
847 for (
int x = 0; x < w; ++x) {
848 const int cx = x >> 1;
849 const int Ys = sample10(yp, y_stride_b, x, y);
855 const uint16_t *row =
reinterpret_cast<const uint16_t *
>(up + cy * uv_stride_b);
856 Cbs =
static_cast<int>(row[cx * 2 + 0]) >> sample_shift;
857 Crs =
static_cast<int>(row[cx * 2 + 1]) >> sample_shift;
859 Cbs = sample10(up, uv_stride_b, cx, cy);
860 Crs = sample10(vp, v_stride_b, cx, cy);
863 const float Y = (Ys - 64) * kInvY;
864 const float Cb = (Cbs - 512) * kInvC;
865 const float Cr = (Crs - 512) * kInvC;
867 float R = Y + kCrR * Cr;
868 float G = Y + kCbG * Cb + kCrG * Cr;
869 float B = Y + kCbB * Cb;
873 R = R < 0.0f ? 0.0f : (R > 1.0f ? 1.0f : R);
874 G = G < 0.0f ? 0.0f : (G > 1.0f ? 1.0f : G);
875 B = B < 0.0f ? 0.0f : (B > 1.0f ? 1.0f : B);
877 dst[x * 4 + 0] =
static_cast<uint16_t
>(R * 65535.0f + 0.5f);
878 dst[x * 4 + 1] =
static_cast<uint16_t
>(G * 65535.0f + 0.5f);
879 dst[x * 4 + 2] =
static_cast<uint16_t
>(B * 65535.0f + 0.5f);
880 dst[x * 4 + 3] = 0xFFFF;
886inline float clamp01f(
float v) {
return (v < 0.0f) ? 0.0f : (v > 1.0f ? 1.0f : v); }
890 constexpr float m1 = 0.1593017578125f;
891 constexpr float m2 = 78.84375f;
892 constexpr float c1 = 0.8359375f;
893 constexpr float c2 = 18.8515625f;
894 constexpr float c3 = 18.6875f;
896 const float p = std::pow(e, 1.0f / m2);
897 const float num = std::max(p - c1, 0.0f);
898 const float den = c2 - c3 * p;
902 return std::pow(num / den, 1.0f / m1);
907 constexpr float a = 0.17883277f;
908 constexpr float b = 0.28466892f;
909 constexpr float c = 0.55991073f;
912 return (e * e) / 3.0f;
914 return (std::exp((e - c) / a) + b) / 12.0f;
920 if (v <= 0.0031308f) {
923 srgb = 1.055f * std::pow(v, 1.0f / 2.4f) - 0.055f;
925 const int iv =
static_cast<int>(srgb * 255.0f + 0.5f);
926 return static_cast<unsigned char>(std::clamp(iv, 0, 255));
932 if (v <= 0.0031308f) {
935 srgb = 1.055f * std::pow(v, 1.0f / 2.4f) - 0.055f;
937 const int iv =
static_cast<int>(srgb * 65535.0f + 0.5f);
938 return static_cast<uint16_t
>(std::clamp(iv, 0, 65535));
942 std::vector<unsigned char> sdr_pixels(
static_cast<size_t>(w) *
static_cast<size_t>(h) * 4, 0);
943 const bool is_hlg = (hdr_trc == AVCOL_TRC_ARIB_STD_B67);
946 constexpr float m00 = 1.6605f, m01 = -0.5876f, m02 = -0.0728f;
947 constexpr float m10 = -0.1246f, m11 = 1.1329f, m12 = -0.0083f;
948 constexpr float m20 = -0.0182f, m21 = -0.1006f, m22 = 1.1187f;
950 auto read_u16_le = [&](
size_t byte_index) -> uint16_t {
return static_cast<uint16_t
>(
static_cast<uint16_t
>(hdr_pixels[byte_index]) | (
static_cast<uint16_t
>(hdr_pixels[byte_index + 1]) << 8)); };
952 for (
int y = 0; y < h; ++y) {
953 for (
int x = 0; x < w; ++x) {
954 const size_t px =
static_cast<size_t>(y) *
static_cast<size_t>(w) +
static_cast<size_t>(x);
955 const size_t src_idx = px * 8;
956 const size_t dst_idx = px * 4;
958 const float enc_r = read_u16_le(src_idx + 0) / 65535.0f;
959 const float enc_g = read_u16_le(src_idx + 2) / 65535.0f;
960 const float enc_b = read_u16_le(src_idx + 4) / 65535.0f;
962 float lin2020_r = 0.0f;
963 float lin2020_g = 0.0f;
964 float lin2020_b = 0.0f;
976 float sr = m00 * lin2020_r + m01 * lin2020_g + m02 * lin2020_b;
977 float sg = m10 * lin2020_r + m11 * lin2020_g + m12 * lin2020_b;
978 float sb = m20 * lin2020_r + m21 * lin2020_g + m22 * lin2020_b;
980 sr = std::max(sr, 0.0f);
981 sg = std::max(sg, 0.0f);
982 sb = std::max(sb, 0.0f);
985 sr = sr / (1.0f + sr);
986 sg = sg / (1.0f + sg);
987 sb = sb / (1.0f + sb);
992 sdr_pixels[dst_idx + 3] = 255;
1003 std::vector<uint16_t> sdr_pixels(
static_cast<size_t>(w) *
static_cast<size_t>(h) * 4, 0);
1004 const bool is_hlg = (hdr_trc == AVCOL_TRC_ARIB_STD_B67);
1006 constexpr float m00 = 1.6605f, m01 = -0.5876f, m02 = -0.0728f;
1007 constexpr float m10 = -0.1246f, m11 = 1.1329f, m12 = -0.0083f;
1008 constexpr float m20 = -0.0182f, m21 = -0.1006f, m22 = 1.1187f;
1010 auto read_u16_le = [&](
size_t byte_index) -> uint16_t {
return static_cast<uint16_t
>(
static_cast<uint16_t
>(hdr_pixels[byte_index]) | (
static_cast<uint16_t
>(hdr_pixels[byte_index + 1]) << 8)); };
1012 for (
int y = 0; y < h; ++y) {
1013 for (
int x = 0; x < w; ++x) {
1014 const size_t px =
static_cast<size_t>(y) *
static_cast<size_t>(w) +
static_cast<size_t>(x);
1015 const size_t src_idx = px * 8;
1016 const size_t dst_idx = px * 4;
1018 const float enc_r = read_u16_le(src_idx + 0) / 65535.0f;
1019 const float enc_g = read_u16_le(src_idx + 2) / 65535.0f;
1020 const float enc_b = read_u16_le(src_idx + 4) / 65535.0f;
1022 float lin2020_r = 0.0f, lin2020_g = 0.0f, lin2020_b = 0.0f;
1033 float sr = m00 * lin2020_r + m01 * lin2020_g + m02 * lin2020_b;
1034 float sg = m10 * lin2020_r + m11 * lin2020_g + m12 * lin2020_b;
1035 float sb = m20 * lin2020_r + m21 * lin2020_g + m22 * lin2020_b;
1037 sr = std::max(sr, 0.0f);
1038 sg = std::max(sg, 0.0f);
1039 sb = std::max(sb, 0.0f);
1041 sr = sr / (1.0f + sr);
1042 sg = sg / (1.0f + sg);
1043 sb = sb / (1.0f + sb);
1048 sdr_pixels[dst_idx + 3] = 65535;
1055#ifdef ACMX2_WITH_WEBP
1056inline bool saveSdrWebPFromRgba8(
const char *filename,
const unsigned char *rgba8,
int width,
int height) {
1057 if (filename ==
nullptr || rgba8 ==
nullptr || width <= 0 || height <= 0) {
1061 uint8_t *output =
nullptr;
1062 const int stride = width * 4;
1063 const size_t out_size = WebPEncodeLosslessRGBA(rgba8, width, height, stride, &output);
1064 if (out_size == 0 || output ==
nullptr) {
1065 if (output !=
nullptr) {
1071 std::ofstream ofs(filename, std::ios::binary);
1072 if (!ofs.is_open()) {
1076 ofs.write(
reinterpret_cast<const char *
>(output),
static_cast<std::streamsize
>(out_size));
1077 const bool ok = ofs.good();
1090inline bool saveHdrWebPFromRgba16(
const char *filename,
const unsigned char *rgba16,
int width,
int height,
int hdr_trc) {
1091 if (filename ==
nullptr || rgba16 ==
nullptr || width <= 0 || height <= 0) {
1094 const size_t pixel_count =
static_cast<size_t>(width) *
static_cast<size_t>(height);
1096 const std::vector<unsigned char> hdr_view(rgba16, rgba16 + pixel_count * 8);
1099 uint8_t *output =
nullptr;
1100 const int stride = width * 4;
1101 const size_t out_size = WebPEncodeLosslessRGBA(rgba8.data(), width, height, stride, &output);
1102 if (out_size == 0 || output ==
nullptr) {
1103 if (output !=
nullptr) {
1109 std::ofstream ofs(filename, std::ios::binary);
1110 if (!ofs.is_open()) {
1114 ofs.write(
reinterpret_cast<const char *
>(output),
static_cast<std::streamsize
>(out_size));
1115 const bool ok = ofs.good();
1122#ifdef ACMX2_WITH_TIFF
1123inline bool saveSdrTiffFromRgba8(
const char *filename,
const unsigned char *rgba8,
int width,
int height) {
1124 if (filename ==
nullptr || rgba8 ==
nullptr || width <= 0 || height <= 0) {
1128 TIFF *tif = TIFFOpen(filename,
"w");
1129 if (tif ==
nullptr) {
1133 TIFFSetField(tif, TIFFTAG_IMAGEWIDTH,
static_cast<uint32_t
>(width));
1134 TIFFSetField(tif, TIFFTAG_IMAGELENGTH,
static_cast<uint32_t
>(height));
1135 TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 4);
1136 TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8);
1137 TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);
1138 TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
1139 TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);
1140 TIFFSetField(tif, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_UINT);
1141 TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_LZW);
1142 TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tif, 0));
1144 const uint16_t extra[1] = {EXTRASAMPLE_UNASSALPHA};
1145 TIFFSetField(tif, TIFFTAG_EXTRASAMPLES, 1, extra);
1146 TIFFSetField(tif, TIFFTAG_IMAGEDESCRIPTION,
"ACMX2 SDR snapshot: 8-bit RGBA TIFF");
1148 const tmsize_t row_bytes =
static_cast<tmsize_t
>(width) * 4;
1150 for (
int y = 0; y < height; ++y) {
1151 unsigned char *row =
const_cast<unsigned char *
>(rgba8 +
static_cast<size_t>(y) *
static_cast<size_t>(row_bytes));
1152 if (TIFFWriteScanline(tif, row,
static_cast<uint32_t
>(y), 0) < 0) {
1168inline bool saveHdrTiffFromRgba16(
const char *filename,
const unsigned char *rgba16,
int width,
int height,
int hdr_trc) {
1169 if (filename ==
nullptr || rgba16 ==
nullptr || width <= 0 || height <= 0) {
1175 TIFF *tif = TIFFOpen(filename,
"w");
1176 if (tif ==
nullptr) {
1180 TIFFSetField(tif, TIFFTAG_IMAGEWIDTH,
static_cast<uint32_t
>(width));
1181 TIFFSetField(tif, TIFFTAG_IMAGELENGTH,
static_cast<uint32_t
>(height));
1182 TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 4);
1183 TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 16);
1184 TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);
1185 TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
1186 TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);
1187 TIFFSetField(tif, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_UINT);
1188 TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_LZW);
1189 TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tif, 0));
1191 const uint16_t extra[1] = {EXTRASAMPLE_UNASSALPHA};
1192 TIFFSetField(tif, TIFFTAG_EXTRASAMPLES, 1, extra);
1193 TIFFSetField(tif, TIFFTAG_IMAGEDESCRIPTION,
"ACMX2 HDR snapshot: 16-bit RGBA, sRGB tone-mapped from BT.2020 PQ/HLG");
1195 const tmsize_t row_samples =
static_cast<tmsize_t
>(width) * 4;
1197 for (
int y = 0; y < height; ++y) {
1199 uint16_t *row =
const_cast<uint16_t *
>(srgb16.data() +
static_cast<size_t>(y) *
static_cast<size_t>(row_samples));
1200 if (TIFFWriteScanline(tif, row,
static_cast<uint32_t
>(y), 0) < 0) {
1234 "layout(location = 0) in vec3 aPos;\n"
1235 "layout(location = 1) in vec2 aTex;\n"
1237 "uniform mat4 mv_matrix;\n"
1238 "uniform mat4 proj_matrix;\n"
1240 " gl_Position = proj_matrix * mv_matrix * vec4(aPos, 1.0);\n"
1247 "uniform sampler2D samp;\n"
1248 "uniform int transfer; // 1 = PQ (SMPTE2084), 2 = HLG (ARIB STD-B67)\n"
1249 "// SMPTE ST.2084 inverse EOTF. Input: PQ code in [0,1]. Output: linear\n"
1250 "// fractional luminance where 1.0 = PQ peak (10000 nits).\n"
1251 "vec3 pqToLinear(vec3 e) {\n"
1252 " const float m1 = 2610.0 / 16384.0;\n"
1253 " const float m2 = (2523.0 / 4096.0) * 128.0;\n"
1254 " const float c1 = 3424.0 / 4096.0;\n"
1255 " const float c2 = (2413.0 / 4096.0) * 32.0;\n"
1256 " const float c3 = (2392.0 / 4096.0) * 32.0;\n"
1257 " vec3 ec = clamp(e, vec3(0.0), vec3(1.0));\n"
1258 " vec3 em2 = pow(ec, vec3(1.0 / m2));\n"
1259 " vec3 num = max(em2 - c1, vec3(0.0));\n"
1260 " // Clamp denominator floor to avoid div-by-zero / sign-flip near\n"
1261 " // peak code values which would otherwise propagate NaN through\n"
1262 " // pow() and read back as zero on some drivers.\n"
1263 " vec3 den = max(c2 - c3 * em2, vec3(1e-6));\n"
1264 " return pow(num / den, vec3(1.0 / m1));\n"
1266 "// ARIB STD-B67 (HLG) inverse OETF. Input HLG code [0,1]. Output scene\n"
1267 "// linear [0,1] normalised so 0.5 HLG -> ~0.083 linear (reference white).\n"
1268 "vec3 hlgToLinear(vec3 e) {\n"
1269 " const float a = 0.17883277;\n"
1270 " const float b = 0.28466892;\n"
1271 " const float c = 0.55991073;\n"
1272 " vec3 ec = clamp(e, vec3(0.0), vec3(1.0));\n"
1273 " vec3 lo = (ec * ec) / 3.0;\n"
1274 " vec3 hi = (exp((ec - c) / a) + b) / 12.0;\n"
1275 " return mix(lo, hi, step(vec3(0.5), ec));\n"
1278 " vec4 raw = texture(samp, tc);\n"
1280 " if (transfer == 2) {\n"
1281 " // HLG: inverse-OETF then scale so reference white == 1.0.\n"
1282 " // Reference white sits at ~0.26 scene-linear; scale by 1/0.26\n"
1283 " // ~= 3.77 so shader colours match SDR at nominal exposure.\n"
1284 " lin = hlgToLinear(raw.rgb) * 3.77358491;\n"
1286 " // PQ: inverse-EOTF, then rescale so 100 nits == 1.0 (reference\n"
1287 " // white). 100 / 10000 = 0.01, so we multiply by 100. Highlights\n"
1288 " // above reference end up as values > 1.0 (legal in RGBA16F).\n"
1289 " lin = pqToLinear(raw.rgb) * 100.0;\n"
1291 " color = vec4(lin, raw.a);\n"
1297 "uniform sampler2D samp;\n"
1298 "uniform int transfer;\n"
1299 "vec3 linearToPq(vec3 L) {\n"
1300 " const float m1 = 2610.0 / 16384.0;\n"
1301 " const float m2 = (2523.0 / 4096.0) * 128.0;\n"
1302 " const float c1 = 3424.0 / 4096.0;\n"
1303 " const float c2 = (2413.0 / 4096.0) * 32.0;\n"
1304 " const float c3 = (2392.0 / 4096.0) * 32.0;\n"
1305 " vec3 Lm = pow(max(L, vec3(0.0)), vec3(m1));\n"
1306 " vec3 num = c1 + c2 * Lm;\n"
1307 " vec3 den = 1.0 + c3 * Lm;\n"
1308 " return pow(num / den, vec3(m2));\n"
1310 "vec3 linearToHlg(vec3 L) {\n"
1311 " const float a = 0.17883277;\n"
1312 " const float b = 0.28466892;\n"
1313 " const float c = 0.55991073;\n"
1314 " vec3 Lc = max(L, vec3(0.0));\n"
1315 " vec3 lo = sqrt(3.0 * Lc);\n"
1316 " // Floor the log argument so the unused 'hi' branch never produces\n"
1317 " // NaN. mix() with NaN is mix(lo, NaN, 0) = lo + NaN*0 = NaN, which\n"
1318 " // would surface as black pixels after the final clamp/UNORM cast.\n"
1319 " vec3 hi = a * log(max(12.0 * Lc - b, vec3(1e-6))) + c;\n"
1320 " return mix(lo, hi, step(vec3(1.0 / 12.0), Lc));\n"
1323 " vec3 lin = texture(samp, tc).rgb;\n"
1324 " // Sanitise potentially-NaN/Inf values from prior user shader\n"
1325 " // computations so they do not survive the OETF and read back as\n"
1326 " // zero (=> black holes in dark/edge regions).\n"
1327 " bvec3 nans = isnan(lin);\n"
1328 " bvec3 infs = isinf(lin);\n"
1329 " lin = mix(lin, vec3(0.0), vec3(nans));\n"
1330 " lin = mix(lin, vec3(0.0), vec3(infs));\n"
1331 " lin = max(lin, vec3(0.0));\n"
1333 " if (transfer == 2) {\n"
1334 " enc = linearToHlg(lin / 3.77358491);\n"
1336 " enc = linearToPq(lin / 100.0);\n"
1338 " color = vec4(clamp(enc, 0.0, 1.0), 1.0);\n"
1344 "layout(location = 0) in vec3 aPos;\n"
1345 "layout(location = 1) in vec2 aTex;\n"
1347 "uniform mat4 mv_matrix;\n"
1348 "uniform mat4 proj_matrix;\n"
1349 "uniform int flip_y; // 1 to flip Y coordinate\n"
1351 " gl_Position = proj_matrix * mv_matrix * vec4(aPos, 1.0);\n"
1352 " vec2 tex = aTex;\n"
1353 " if (flip_y == 1) {\n"
1354 " tex.y = 1.0 - tex.y;\n"
1362 "uniform sampler2D samp;\n"
1364 " color = texture(samp, tc);\n"
1370 "uniform sampler2D samp;\n"
1371 "uniform float time_f;\n"
1372 "uniform vec2 iResolution;\n"
1373 "uniform float alpha;\n"
1374 "void main(void) {\n"
1375 " vec2 uv = (tc * 2.0 - 1.0);\n"
1376 " float aspect = iResolution.x / iResolution.y;\n"
1377 " uv.x *= aspect;\n"
1378 " float d = length(uv);\n"
1379 " float lensStrength = 1.5;\n"
1380 " vec3 normal = normalize(vec3(uv, 1.0 / lensStrength));\n"
1381 " float fisheyeRadius = atan(d, 1.0);\n"
1382 " vec2 distortedUV = normalize(uv + 1e-6) * fisheyeRadius;\n"
1383 " float t = time_f * 0.8;\n"
1384 " float r_dist = length(distortedUV);\n"
1385 " float angle = atan(distortedUV.y, distortedUV.x);\n"
1386 " float spiral = angle + (log(r_dist + 0.1) * 3.0) - t * 1.5;\n"
1387 " float r = sin(spiral * 3.0 + t);\n"
1388 " float g = sin(spiral * 3.0 + t + 2.094);\n"
1389 " float b = sin(spiral * 3.0 + t + 4.188);\n"
1390 " vec3 spiralCol = vec3(r, g, b) * 0.5 + 0.5;\n"
1391 " vec3 lightDir = normalize(vec3(sin(time_f), cos(time_f), 1.0));\n"
1392 " float diff = max(dot(normal, lightDir), 0.0);\n"
1393 " float spec = pow(max(dot(reflect(-lightDir, normal), vec3(0,0,1)), 0.0), 16.0);\n"
1394 " vec4 texColor = texture(samp, tc);\n"
1395 " vec3 finalCol = mix(texColor.rgb, spiralCol * (diff + 0.5) + spec, 0.7);\n"
1396 " finalCol *= smoothstep(2.0, 0.5, d);\n"
1397 " float finalAlpha = texColor.a * alpha;\n"
1398 " color = vec4(finalCol, finalAlpha);\n"
1405 bool open(
const std::string &filename,
bool prefer_cuda) {
1408 if (avformat_open_input(&
format_ctx, filename.c_str(),
nullptr,
nullptr) < 0) {
1411 if (avformat_find_stream_info(
format_ctx,
nullptr) < 0) {
1421 const AVCodec *codec = avcodec_find_decoder(stream->codecpar->codec_id);
1426 codec_ctx = avcodec_alloc_context3(codec);
1431 if (avcodec_parameters_to_context(
codec_ctx, stream->codecpar) < 0) {
1435 codec_ctx->thread_count = std::max(1u, std::thread::hardware_concurrency());
1437#ifdef ACMX2_WITH_CUDA
1442 static_cast<void>(prefer_cuda);
1445 if (avcodec_open2(
codec_ctx, codec,
nullptr) < 0) {
1451 packet = av_packet_alloc();
1458 fps = av_q2d(stream->avg_frame_rate);
1460 fps = av_q2d(stream->r_frame_rate);
1462 frame_count =
static_cast<double>(stream->nb_frames);
1468 const AVCodecParameters *par = stream->codecpar;
1469 const bool primaries_hdr = (par->color_primaries == AVCOL_PRI_BT2020);
1470 const bool trc_hdr = (par->color_trc == AVCOL_TRC_SMPTE2084) || (par->color_trc == AVCOL_TRC_ARIB_STD_B67) || (par->color_trc == AVCOL_TRC_BT2020_10) || (par->color_trc == AVCOL_TRC_BT2020_12);
1471 const bool space_hdr = (par->color_space == AVCOL_SPC_BT2020_NCL) || (par->color_space == AVCOL_SPC_BT2020_CL);
1472 int bpp = par->bits_per_raw_sample;
1477 const AVPixelFormat pf =
codec_ctx->pix_fmt;
1478 if (pf != AV_PIX_FMT_NONE) {
1479 const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(pf);
1480 if (desc && desc->nb_components > 0) {
1481 bpp = desc->comp[0].depth;
1485 const bool depth_hdr = (bpp >= 10);
1486 is_hdr = (primaries_hdr || trc_hdr || space_hdr) && depth_hdr;
1499 for (
int i = 0; i < par->nb_coded_side_data; ++i) {
1500 const AVPacketSideData &sd = par->coded_side_data[i];
1501 if (sd.type == AV_PKT_DATA_MASTERING_DISPLAY_METADATA) {
1503 }
else if (sd.type == AV_PKT_DATA_CONTENT_LIGHT_LEVEL) {
1527 if (read_ret >= 0) {
1536 if (avcodec_send_packet(
codec_ctx,
nullptr) < 0) {
1545 if (receive_ret == AVERROR(EAGAIN)) {
1551 if (receive_ret == AVERROR_EOF) {
1554 if (receive_ret < 0) {
1573 if (read_ret >= 0) {
1583 if (avcodec_send_packet(
codec_ctx,
nullptr) < 0) {
1593 if (receive_ret == AVERROR(EAGAIN)) {
1599 if (receive_ret == AVERROR_EOF) {
1602 if (receive_ret < 0) {
1621 sws_h = src->height;
1630 out_bgr.create(src->height, src->width, CV_8UC3);
1631 uint8_t *dst_data[4] = {out_bgr.data,
nullptr,
nullptr,
nullptr};
1632 int dst_linesize[4] = {
static_cast<int>(out_bgr.step), 0, 0, 0};
1634 sws_scale(
sws_ctx, src->data, src->linesize, 0, src->height, dst_data, dst_linesize);
1659 if (read_ret >= 0) {
1669 if (avcodec_send_packet(
codec_ctx,
nullptr) < 0) {
1679 if (receive_ret == AVERROR(EAGAIN)) {
1685 if (receive_ret == AVERROR_EOF) {
1688 if (receive_ret < 0) {
1707 if (src->format == AV_PIX_FMT_YUV420P10LE || src->format == AV_PIX_FMT_P010LE) {
1743 int src_space = SWS_CS_DEFAULT;
1744 switch (src->colorspace) {
1745 case AVCOL_SPC_BT2020_NCL:
1746 case AVCOL_SPC_BT2020_CL:
1747 src_space = SWS_CS_BT2020;
1749 case AVCOL_SPC_BT709:
1750 src_space = SWS_CS_ITU709;
1752 case AVCOL_SPC_SMPTE170M:
1753 case AVCOL_SPC_BT470BG:
1754 src_space = SWS_CS_ITU601;
1757 src_space = SWS_CS_BT2020;
1760 const int src_range = (src->color_range == AVCOL_RANGE_JPEG) ? 1 : 0;
1761 const int *src_coefs = sws_getCoefficients(src_space);
1762 const int *dst_coefs = sws_getCoefficients(SWS_CS_BT2020);
1763 sws_setColorspaceDetails(
sws_ctx_hdr, src_coefs, src_range, dst_coefs, 1 , 0 , 1 << 16 , 1 << 16 );
1766 out_rgba16.create(src->height, src->width, CV_16UC4);
1767 uint8_t *dst_data[4] = {out_rgba16.data,
nullptr,
nullptr,
nullptr};
1768 int dst_linesize[4] = {
static_cast<int>(out_rgba16.step), 0, 0, 0};
1770 sws_scale(
sws_ctx_hdr, src->data, src->linesize, 0, src->height, dst_data, dst_linesize);
1809 static AVPixelFormat
getHwFormat(AVCodecContext *ctx,
const AVPixelFormat *pix_fmts) {
1815 for (
const AVPixelFormat *p = pix_fmts; *p != AV_PIX_FMT_NONE; ++p) {
1816 if (*p == self->hw_pix_fmt) {
1824#ifdef ACMX2_WITH_CUDA
1825 for (
int i = 0;; ++i) {
1826 const AVCodecHWConfig *cfg = avcodec_get_hw_config(codec, i);
1831 if ((cfg->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX) && cfg->device_type == AV_HWDEVICE_TYPE_CUDA) {
1841 if (av_hwdevice_ctx_create(&
hw_device_ctx, AV_HWDEVICE_TYPE_CUDA,
nullptr,
nullptr, 0) < 0) {
1850 static_cast<void>(codec);
1971 for (
size_t i = 0; i < threads; ++i)
1974 std::function<
void()> task;
1976 std::unique_lock<std::mutex> lock(this->
queue_mutex);
1978 if (this->
stop && this->
tasks.empty()) {
1981 if (this->
tasks.empty()) {
1984 task = std::move(this->
tasks.front());
2007 throw std::runtime_error(
"enqueue on stopped SnapshotThreadPool");
2008 tasks.emplace(std::forward<F>(f));
2027 for (std::thread &worker :
workers)
2077 void init(
int w,
int h,
bool hdr =
false) {
2093 const GLint internal = hdr ? GL_RGBA16F : GL_RGBA;
2094 const GLenum type = hdr ? GL_HALF_FLOAT : GL_UNSIGNED_BYTE;
2095 const std::size_t bytes_per_pixel = hdr ? 8u : 4u;
2096 std::vector<unsigned char> zeros(
static_cast<size_t>(w) *
static_cast<size_t>(h) * bytes_per_pixel, 0);
2097 for (std::size_t i = 0; i <
num_frames; ++i) {
2098 glBindTexture(GL_TEXTURE_2D,
textures[i]);
2099 glTexImage2D(GL_TEXTURE_2D, 0, internal, w, h, 0, GL_RGBA, type, zeros.data());
2100 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
2101 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
2102 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
2103 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
2105 glBindTexture(GL_TEXTURE_2D, 0);
2146 GLint prev_read = 0;
2147 glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &prev_read);
2148 glBindFramebuffer(GL_READ_FRAMEBUFFER, src_fbo);
2149 glReadBuffer(GL_COLOR_ATTACHMENT0);
2160 for (std::size_t i = 0; i <
num_frames; ++i) {
2161 glCopyTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0,
static_cast<GLint
>(i), 0, 0, w, h);
2164 glCopyTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0,
static_cast<GLint
>(
head), 0, 0, w, h);
2166 glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
2167 glBindFramebuffer(GL_READ_FRAMEBUFFER, prev_read);
2173 const GLint internal =
is_hdr ? GL_RGBA16F : GL_RGBA;
2174 const GLenum type =
is_hdr ? GL_HALF_FLOAT : GL_UNSIGNED_BYTE;
2176 glBindTexture(GL_TEXTURE_2D, texture);
2177 glTexImage2D(GL_TEXTURE_2D, 0, internal, w, h, 0, GL_RGBA, type,
nullptr);
2178 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
2179 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
2180 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
2181 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
2190 glBindTexture(GL_TEXTURE_2D, texture);
2191 glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, w, h);
2195 glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, w, h);
2197 glBindTexture(GL_TEXTURE_2D, 0);
2198 glBindFramebuffer(GL_READ_FRAMEBUFFER, prev_read);
2217 cv::cvtColor(frame, tmp, cv::COLOR_BGR2RGBA);
2220 glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0,
static_cast<GLint
>(
head), tmp.cols, tmp.rows, 1, GL_RGBA, GL_UNSIGNED_BYTE, tmp.ptr());
2221 glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
2227 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, tmp.cols, tmp.rows, GL_RGBA, GL_UNSIGNED_BYTE, tmp.ptr());
2228 glBindTexture(GL_TEXTURE_2D, 0);
2251 GLint prev_read = 0;
2252 glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &prev_read);
2253 glBindFramebuffer(GL_READ_FRAMEBUFFER,
scratch_fbo);
2254 glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, src_tex, 0);
2255 glReadBuffer(GL_COLOR_ATTACHMENT0);
2266 for (std::size_t i = 0; i <
num_frames; ++i) {
2267 glCopyTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0,
static_cast<GLint
>(i), 0, 0, w, h);
2270 glCopyTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0,
static_cast<GLint
>(
head), 0, 0, w, h);
2272 glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
2273 glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
2274 glBindFramebuffer(GL_READ_FRAMEBUFFER, prev_read);
2280 const GLint internal =
is_hdr ? GL_RGBA16F : GL_RGBA;
2281 const GLenum type =
is_hdr ? GL_HALF_FLOAT : GL_UNSIGNED_BYTE;
2283 glBindTexture(GL_TEXTURE_2D, texture);
2284 glTexImage2D(GL_TEXTURE_2D, 0, internal, w, h, 0, GL_RGBA, type,
nullptr);
2285 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
2286 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
2287 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
2288 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
2297 glBindTexture(GL_TEXTURE_2D, texture);
2298 glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, w, h);
2302 glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, w, h);
2304 glBindTexture(GL_TEXTURE_2D, 0);
2306 glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
2307 glBindFramebuffer(GL_READ_FRAMEBUFFER, prev_read);
2344 cv::cvtColor(frame, tmp, cv::COLOR_BGR2RGBA);
2345 const bool size_matches = (tmp.cols ==
width && tmp.rows ==
height);
2347 if (!size_matches) {
2353 for (std::size_t i = 0; i <
num_frames; ++i) {
2354 glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0,
static_cast<GLint
>(i), tmp.cols, tmp.rows, 1, GL_RGBA, GL_UNSIGNED_BYTE, tmp.ptr());
2356 glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
2362 for (std::size_t i = 0; i <
num_frames; ++i) {
2363 glBindTexture(GL_TEXTURE_2D,
textures[i]);
2364 if (!size_matches) {
2365 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tmp.cols, tmp.rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, tmp.ptr());
2367 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, tmp.cols, tmp.rows, GL_RGBA, GL_UNSIGNED_BYTE, tmp.ptr());
2370 glBindTexture(GL_TEXTURE_2D, 0);
2371 if (!size_matches) {
2408 const GLint internal =
is_hdr ? GL_RGBA16F : GL_RGBA;
2409 const GLenum type =
is_hdr ? GL_HALF_FLOAT : GL_UNSIGNED_BYTE;
2410 glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, internal,
width,
height,
static_cast<GLsizei
>(
num_frames), 0, GL_RGBA, type,
nullptr);
2411 glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
2412 glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
2413 glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
2414 glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
2415 glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
2449#ifdef ACMX2_WITH_CUDA
2450 cudaGraphicsResource *cudaTexResource =
nullptr;
2451 cudaStream_t uploadStream =
nullptr;
2452 cudaEvent_t inputReadyEvent =
nullptr;
2477 glBindTexture(GL_TEXTURE_2D,
textureID);
2480 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8,
width,
height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
2481 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
2482 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
2483 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
2484 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
2485 glBindTexture(GL_TEXTURE_2D, 0);
2486#ifdef ACMX2_WITH_CUDA
2490 CHECK_CUDA(cudaGraphicsGLRegisterImage(&cudaTexResource,
textureID, GL_TEXTURE_2D, cudaGraphicsRegisterFlagsWriteDiscard));
2491 if (!uploadStream) {
2492 CHECK_CUDA(cudaStreamCreateWithFlags(&uploadStream, cudaStreamNonBlocking));
2494 if (!inputReadyEvent) {
2495 CHECK_CUDA(cudaEventCreateWithFlags(&inputReadyEvent, cudaEventDisableTiming));
2500#ifdef ACMX2_WITH_CUDA
2517 void update(
const cv::cuda::GpuMat &gpuFrame) {
2518 if (gpuFrame.cols !=
width || gpuFrame.rows !=
height) {
2519 init(gpuFrame.cols, gpuFrame.rows);
2521 cudaArray_t texArray =
nullptr;
2525 CHECK_CUDA(cudaEventRecord(inputReadyEvent,
nullptr));
2526 CHECK_CUDA(cudaStreamWaitEvent(uploadStream, inputReadyEvent, 0));
2527 CHECK_CUDA(cudaGraphicsMapResources(1, &cudaTexResource, uploadStream));
2528 CHECK_CUDA(cudaGraphicsSubResourceGetMappedArray(&texArray, cudaTexResource, 0, 0));
2529 CHECK_CUDA(cudaMemcpy2DToArrayAsync(texArray, 0, 0, gpuFrame.data, gpuFrame.step,
static_cast<size_t>(
width) * 4,
static_cast<size_t>(
height), cudaMemcpyDeviceToDevice, uploadStream));
2530 CHECK_CUDA(cudaGraphicsUnmapResources(1, &cudaTexResource, uploadStream));
2543#ifdef ACMX2_WITH_CUDA
2544 if (cudaTexResource) {
2545 CHECK_CUDA(cudaGraphicsUnregisterResource(cudaTexResource));
2546 cudaTexResource =
nullptr;
2549 cudaStreamDestroy(uploadStream);
2550 uploadStream =
nullptr;
2552 if (inputReadyEvent) {
2553 cudaEventDestroy(inputReadyEvent);
2554 inputReadyEvent =
nullptr;
2626 bool save(
const std::string &path)
const {
2628 std::filesystem::path parent = std::filesystem::path(path).parent_path();
2629 if (!parent.empty() && !std::filesystem::exists(parent, ec)) {
2630 std::filesystem::create_directories(parent, ec);
2632 mx::system_err <<
"acmx2: Could not create cache directory '" << parent.string() <<
"': " << ec.message() <<
"\n";
2636 std::ofstream file(path, std::ios::binary);
2637 if (!file.is_open()) {
2638 mx::system_err <<
"acmx2: Could not open cache file for writing: '" << path <<
"' (errno=" << errno <<
" - " << std::strerror(errno) <<
")\n";
2645 auto writeString = [&file](
const std::string &s) {
2646 uint32_t len =
static_cast<uint32_t
>(s.size());
2647 file.write(
reinterpret_cast<const char *
>(&len),
sizeof(len));
2648 file.write(s.data(), len);
2655 uint32_t count =
static_cast<uint32_t
>(
entries.size());
2656 file.write(
reinterpret_cast<const char *
>(&count),
sizeof(count));
2658 for (
const auto &e :
entries) {
2659 writeString(e.shader_name);
2660 const uint8_t kind =
static_cast<uint8_t
>(e.kind);
2661 file.write(
reinterpret_cast<const char *
>(&kind),
sizeof(kind));
2662 uint8_t failed_flag = e.failed ? 1 : 0;
2663 file.write(
reinterpret_cast<const char *
>(&failed_flag),
sizeof(failed_flag));
2664 file.write(
reinterpret_cast<const char *
>(&e.source_hash),
sizeof(e.source_hash));
2665 file.write(
reinterpret_cast<const char *
>(&e.format_2d),
sizeof(e.format_2d));
2666 uint32_t size_2d =
static_cast<uint32_t
>(e.binary_2d.size());
2667 file.write(
reinterpret_cast<const char *
>(&size_2d),
sizeof(size_2d));
2668 file.write(e.binary_2d.data(), size_2d);
2670 file.write(
reinterpret_cast<const char *
>(&e.format_3d),
sizeof(e.format_3d));
2671 uint32_t size_3d =
static_cast<uint32_t
>(e.binary_3d.size());
2672 file.write(
reinterpret_cast<const char *
>(&size_3d),
sizeof(size_3d));
2673 file.write(e.binary_3d.data(), size_3d);
2693 bool load(
const std::string &path) {
2694 std::ifstream file(path, std::ios::binary);
2695 if (!file.is_open())
2698 uint32_t magic, version;
2699 file.read(
reinterpret_cast<char *
>(&magic),
sizeof(magic));
2700 file.read(
reinterpret_cast<char *
>(&version),
sizeof(version));
2705 auto readString = [&file]() -> std::string {
2707 file.read(
reinterpret_cast<char *
>(&len),
sizeof(len));
2708 std::string s(len,
'\0');
2709 file.read(s.data(), len);
2718 file.read(
reinterpret_cast<char *
>(&count),
sizeof(count));
2722 e.shader_name = readString();
2724 file.read(
reinterpret_cast<char *
>(&kind),
sizeof(kind));
2728 uint8_t failed_flag = 0;
2729 file.read(
reinterpret_cast<char *
>(&failed_flag),
sizeof(failed_flag));
2730 e.failed = (failed_flag != 0);
2731 file.read(
reinterpret_cast<char *
>(&e.source_hash),
sizeof(e.source_hash));
2732 file.read(
reinterpret_cast<char *
>(&e.format_2d),
sizeof(e.format_2d));
2734 file.read(
reinterpret_cast<char *
>(&size_2d),
sizeof(size_2d));
2735 e.binary_2d.resize(size_2d);
2736 file.read(e.binary_2d.data(), size_2d);
2738 file.read(
reinterpret_cast<char *
>(&e.format_3d),
sizeof(e.format_3d));
2740 file.read(
reinterpret_cast<char *
>(&size_3d),
sizeof(size_3d));
2741 e.binary_3d.resize(size_3d);
2742 file.read(e.binary_3d.data(), size_3d);
2758 glGetProgramBinaryFunc =
reinterpret_cast<PFNGLGETPROGRAMBINARYPROC_LOCAL
>(SDL_GL_GetProcAddress(
"glGetProgramBinary"));
2759 glProgramBinaryFunc =
reinterpret_cast<PFNGLPROGRAMBINARYPROC_LOCAL
>(SDL_GL_GetProcAddress(
"glProgramBinary"));
2762 mx::system_err <<
"acmx2: Failed to load glGetProgramBinary/glProgramBinary functions\n";
2835 glBindTexture(GL_TEXTURE_1D,
textureID);
2841 glTexImage1D(GL_TEXTURE_1D, 0, GL_R32F,
bins, 0, GL_RED, GL_FLOAT,
nullptr);
2845 glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
2846 glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
2849 glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
2851 glBindTexture(GL_TEXTURE_1D, 0);
2872 glBindTexture(GL_TEXTURE_1D,
textureID);
2873 glTexSubImage1D(GL_TEXTURE_1D, 0, 0,
bins, GL_RED, GL_FLOAT, mags.data());
2874 glBindTexture(GL_TEXTURE_1D, 0);
2883 for (
size_t i = 0; i < mags.size(); ++i)
2885 glBindTexture(GL_TEXTURE_1D,
textureID);
2886 glTexSubImage1D(GL_TEXTURE_1D, 0, 0,
bins, GL_RED, GL_FLOAT,
scaled_buf.data());
2887 glBindTexture(GL_TEXTURE_1D, 0);
2901 glActiveTexture(GL_TEXTURE0 + unit);
2902 glBindTexture(GL_TEXTURE_1D,
textureID);
2962 if (requested_count <= 0)
2965 GLint max_layers = 0;
2966 glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &max_layers);
2967 if (max_layers <= 0) {
2968 mx::system_err <<
"acmx2: GL_TEXTURE_1D_ARRAY is unavailable on this context\n";
2971 layer_count = std::min(requested_count,
static_cast<int>(max_layers));
2973 mx::system_err <<
"acmx2: --enable-audio-buffers clamped to GPU "
2974 "array-layer limit "
2975 <<
layer_count <<
" (was " << requested_count <<
")\n";
2979 std::vector<float> zeros(
static_cast<size_t>(
bins) *
static_cast<size_t>(
layer_count), 0.0f);
2981 glBindTexture(GL_TEXTURE_1D_ARRAY,
texture_id);
2982 glTexImage2D(GL_TEXTURE_1D_ARRAY, 0, GL_R32F,
bins,
layer_count, 0, GL_RED, GL_FLOAT, zeros.data());
2983 glTexParameteri(GL_TEXTURE_1D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
2984 glTexParameteri(GL_TEXTURE_1D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
2985 glTexParameteri(GL_TEXTURE_1D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
2986 glBindTexture(GL_TEXTURE_1D_ARRAY, 0);
3003 const float *src = mags.data();
3004 if (scale != 0.0f) {
3006 for (
size_t i = 0; i < mags.size(); ++i)
3010 glBindTexture(GL_TEXTURE_1D_ARRAY,
texture_id);
3011 glTexSubImage2D(GL_TEXTURE_1D_ARRAY, 0, 0,
write_idx,
bins, 1, GL_RED, GL_FLOAT, src);
3012 glBindTexture(GL_TEXTURE_1D_ARRAY, 0);
3021 glBindTexture(GL_TEXTURE_1D_ARRAY,
texture_id);
3022 glActiveTexture(GL_TEXTURE0);
3091 glm::vec4
optx = glm::vec4(0.5f, 0.5f, 0.5f, 0.5f);
3179 std::filesystem::path lib(library_path);
3180 std::filesystem::path abs_lib = std::filesystem::absolute(lib, ec);
3181 std::string key = ec ? library_path : abs_lib.lexically_normal().string();
3187 key +=
"|a=" + std::to_string(use_array ? 1 : 0);
3188 std::hash<std::string> hasher;
3189 std::ostringstream name_stream;
3190 name_stream <<
".shader_cache_" << std::hex << hasher(key);
3191 const std::string filename = name_stream.str();
3193 auto isWritable = [](
const std::filesystem::path &dir) {
3197 if (!std::filesystem::exists(dir, e)) {
3198 std::filesystem::create_directories(dir, e);
3203 std::filesystem::path probe = dir /
".acmx2_write_probe";
3204 std::ofstream f(probe, std::ios::binary);
3205 bool ok = f.is_open();
3208 std::filesystem::remove(probe, e);
3212 std::filesystem::path assets(assets_path);
3213 std::filesystem::path libdir(library_path);
3216 std::filesystem::path assets_cache = assets.empty() ? std::filesystem::path() : assets / filename;
3217 std::filesystem::path lib_cache = libdir / filename;
3218 if (!assets.empty() && std::filesystem::exists(assets_cache, ec)) {
3219 return assets_cache.string();
3221 if (std::filesystem::exists(lib_cache, ec)) {
3222 return lib_cache.string();
3225 if (!assets.empty() && isWritable(assets)) {
3226 return assets_cache.string();
3228 if (isWritable(libdir)) {
3229 return lib_cache.string();
3233 return assets.empty() ? lib_cache.string() : assets_cache.string();
3271 std::ifstream in(frag_path);
3274 std::ostringstream ss;
3276 std::string src = ss.str();
3278 std::size_t v_pos = src.find(
"#version");
3279 std::size_t insert_pos = 0;
3280 if (v_pos != std::string::npos) {
3281 std::size_t nl = src.find(
'\n', v_pos);
3282 insert_pos = (nl == std::string::npos) ? src.size() : nl + 1;
3284 std::string define =
"#define SIZE " + std::to_string(
size) +
"\n" +
"#define USE_HISTORY_TEXTURE_ARRAY " + std::to_string(use_array ? 1 : 0) +
"\n";
3285 std::string sourceWithoutComments;
3286 sourceWithoutComments.reserve(src.size());
3287 bool lineComment =
false;
3288 bool blockComment =
false;
3289 for (std::size_t i = 0; i < src.size(); ++i) {
3291 if (src[i] ==
'\n') {
3292 lineComment =
false;
3293 sourceWithoutComments.push_back(
'\n');
3295 sourceWithoutComments.push_back(
' ');
3300 if (src[i] ==
'*' && i + 1 < src.size() && src[i + 1] ==
'/') {
3301 sourceWithoutComments.append(
" ");
3303 blockComment =
false;
3305 sourceWithoutComments.push_back(src[i] ==
'\n' ?
'\n' :
' ');
3309 if (src[i] ==
'/' && i + 1 < src.size() && src[i + 1] ==
'/') {
3310 sourceWithoutComments.append(
" ");
3313 }
else if (src[i] ==
'/' && i + 1 < src.size() && src[i + 1] ==
'*') {
3314 sourceWithoutComments.append(
" ");
3316 blockComment =
true;
3318 sourceWithoutComments.push_back(src[i]);
3324 if (referenced && !declared) {
3328 src.insert(insert_pos, define);
3336 uint64_t hash = 1469598103934665603ull;
3337 for (
unsigned char byte : source) {
3339 hash *= 1099511628211ull;
3354 bool loadProgramWithSize(gl::ShaderProgram *prog,
const std::string &vert_path,
const std::string &frag_path)
const {
3356 if (frag_src.empty()) {
3359 return prog->loadProgram(vert_path, frag_path);
3361 std::ifstream vin(vert_path);
3362 if (!vin.is_open()) {
3363 return prog->loadProgram(vert_path, frag_path);
3365 std::ostringstream vss;
3367 return prog->loadProgramFromText(vss.str(), frag_src);
3388 return std::make_unique<ac::ShaderProgram>();
3389 return std::make_unique<gl::ShaderProgram>();
3393 std::unique_ptr<gl::ShaderProgram>
makeComputeProgram(
const std::string &compute_path, std::string &error)
const {
3395 error =
"OpenGL 4.3 compute shaders are unavailable";
3400 if (source.empty()) {
3401 error =
"Could not read compute shader: " + compute_path;
3406 GLuint cached_program = 0;
3408 mx::system_out <<
"acmx2: Loaded cached compute shader: " << compute_path <<
"\n";
3409 return std::make_unique<gl::ShaderProgram>(cached_program);
3413 const GLuint
shader = glCreateShader(GL_COMPUTE_SHADER);
3415 error =
"Could not create compute shader object";
3418 const char *source_ptr = source.c_str();
3419 glShaderSource(
shader, 1, &source_ptr,
nullptr);
3421 GLint compiled = GL_FALSE;
3422 glGetShaderiv(
shader, GL_COMPILE_STATUS, &compiled);
3423 if (compiled != GL_TRUE) {
3424 error =
"Compute shader compilation failed: " + compute_path;
3427 error +=
"\n" + log;
3432 const GLuint program = glCreateProgram();
3435 error =
"Could not create compute program";
3438 glAttachShader(program,
shader);
3439#if !defined(__APPLE__)
3440 if (glProgramParameteri !=
nullptr) {
3441 glProgramParameteri(program, GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GL_TRUE);
3444 glLinkProgram(program);
3445 GLint linked = GL_FALSE;
3446 glGetProgramiv(program, GL_LINK_STATUS, &linked);
3447 if (linked != GL_TRUE) {
3448 error =
"Compute program link failed: " + compute_path;
3451 error +=
"\n" + log;
3452 glDeleteProgram(program);
3457 glDetachShader(program,
shader);
3462 return std::make_unique<gl::ShaderProgram>(program);
3470 std::vector<char> buffer(
static_cast<std::size_t
>(
length),
'\0');
3471 GLsizei written = 0;
3472 glGetShaderInfoLog(
shader,
length, &written, buffer.data());
3473 std::size_t used =
static_cast<std::size_t
>(std::max<GLsizei>(written, 0));
3474 while (used > 0 && buffer[used - 1] ==
'\0')
3476 return std::string(buffer.data(), used);
3481 glGetProgramiv(program, GL_INFO_LOG_LENGTH, &
length);
3484 std::vector<char> buffer(
static_cast<std::size_t
>(
length),
'\0');
3485 GLsizei written = 0;
3486 glGetProgramInfoLog(program,
length, &written, buffer.data());
3487 std::size_t used =
static_cast<std::size_t
>(std::max<GLsizei>(written, 0));
3488 while (used > 0 && buffer[used - 1] ==
'\0')
3490 return std::string(buffer.data(), used);
3494 std::unique_ptr<gl::ShaderProgram>
compileProgramForReload(
const std::string &vert_path,
const std::string &frag_path, std::string &error)
const {
3497 std::ifstream vertex_file(vert_path);
3498 if (!vertex_file.is_open()) {
3499 error =
"Could not read vertex shader: " + vert_path;
3502 std::ostringstream vertex_stream;
3503 vertex_stream << vertex_file.rdbuf();
3504 const std::string vertex_source = vertex_stream.str();
3506 if (fragment_source.empty()) {
3507 error =
"Could not read fragment shader: " + frag_path;
3511 const auto compile_stage = [&error](GLenum type,
const std::string &source,
const std::string &label) -> GLuint {
3512 const GLuint
shader = glCreateShader(type);
3514 error =
"Could not create " + label +
" shader object";
3517 const char *source_ptr = source.c_str();
3518 glShaderSource(
shader, 1, &source_ptr,
nullptr);
3520 GLint compiled = GL_FALSE;
3521 glGetShaderiv(
shader, GL_COMPILE_STATUS, &compiled);
3522 if (compiled != GL_TRUE) {
3523 error = label +
" shader compilation failed";
3526 error +=
":\n" + log;
3533 const GLuint vertex_shader = compile_stage(GL_VERTEX_SHADER, vertex_source, vert_path);
3534 if (vertex_shader == 0)
3536 const GLuint fragment_shader = compile_stage(GL_FRAGMENT_SHADER, fragment_source, frag_path);
3537 if (fragment_shader == 0) {
3538 glDeleteShader(vertex_shader);
3542 const GLuint program = glCreateProgram();
3544 glDeleteShader(vertex_shader);
3545 glDeleteShader(fragment_shader);
3546 error =
"Could not create shader program";
3549 glAttachShader(program, vertex_shader);
3550 glAttachShader(program, fragment_shader);
3551 glLinkProgram(program);
3552 GLint linked = GL_FALSE;
3553 glGetProgramiv(program, GL_LINK_STATUS, &linked);
3554 if (linked != GL_TRUE) {
3555 error =
"Shader program link failed";
3558 error +=
":\n" + log;
3559 glDeleteProgram(program);
3560 glDeleteShader(vertex_shader);
3561 glDeleteShader(fragment_shader);
3565 glDetachShader(program, vertex_shader);
3566 glDetachShader(program, fragment_shader);
3567 glDeleteShader(vertex_shader);
3568 glDeleteShader(fragment_shader);
3570 return std::make_unique<gl::ShaderProgram>(program);
3592 static constexpr const char *kPassthroughFrag =
"#version 330 core\n"
3595 "uniform sampler2D samp;\n"
3597 " color = texture(samp, tc);\n"
3600 std::ifstream vf(vert_path);
3603 std::stringstream vss;
3605 std::string vert_source = vss.str();
3608 prog->setSilent(
true);
3609 if (!prog->loadProgramFromText(vert_source, kPassthroughFrag)) {
3623 const bool namesChanged =
custom_uniforms.size() != uniforms.size() || !std::equal(
custom_uniforms.begin(),
custom_uniforms.end(), uniforms.begin(), [](
const auto &left,
const auto &right) { return left.name == right.name; });
3635 const auto refreshLocations = [
this](
auto &names,
const auto &programs) {
3636 for (
auto &[
index, data] : names) {
3638 if (
index < 0 ||
static_cast<std::size_t
>(
index) >= programs.size())
3641 data.custom_uniform_locs[i] = glGetUniformLocation(programs[
static_cast<std::size_t
>(
index)]->
id(),
custom_uniforms[i].name.c_str());
3653 if (idx >= 0 && idx < 4)
3735 if (!compute_program)
3736 throw mx::Exception(error);
3737 programs_2d.push_back(std::move(compute_program));
3742 throw mx::Exception(
"Error loading 2D shader program: " + text);
3748 throw mx::Exception(
"Could not create compute fallback: " + text);
3751 mx::system_out <<
"acmx2: Skipping compute shader on this context: " << text <<
"\n";
3761 throw mx::Exception(
"Could not create 3D compute passthrough: " + text);
3766 throw mx::Exception(
"Error loading 3D shader program: " + text);
3770 mx::system_out <<
"acmx2: Compiled Shader 0 (2D+3D): " << text <<
" ✔ \n";
3772 mx::system_out <<
"acmx2: Compiled Shader 0 (2D): " << text <<
" ✔ \n";
3783 bool reloadProgram(gl::GLWindow *win,
size_t shader_index,
const std::string &fragment_path, std::string &error) {
3785 error =
"Shader reload index is outside the loaded library: " + std::to_string(shader_index);
3789 error =
"Shader reload index is outside the loaded 3D library: " + std::to_string(shader_index);
3795 if (!replacement_2d)
3798 std::unique_ptr<gl::ShaderProgram> replacement_3d;
3802 if (!replacement_3d)
3803 error =
"Could not create 3D passthrough for compute shader";
3807 if (!replacement_3d)
3811 std::unordered_map<int, ProgramData> replacement_names_2d;
3812 std::unordered_map<int, ProgramData> replacement_names_3d;
3814 setupProgramUniforms(win, replacement_2d.get(), replacement_names_2d, shader_index, fragment_path);
3816 setupProgramUniforms(win, replacement_3d.get(), replacement_names_3d, shader_index, fragment_path);
3818 }
catch (
const std::exception &e) {
3819 error = std::string(
"Shader uniform setup failed: ") + e.what();
3823 error =
"Shader uniform setup failed with an unknown error";
3828 const int index_key =
static_cast<int>(shader_index);
3829 const auto names_2d_it = replacement_names_2d.find(index_key);
3831 if (names_2d_it == replacement_names_2d.end()) {
3832 error =
"Shader reload did not produce 2D uniform metadata";
3837 error =
"Loaded shader is missing 2D uniform metadata";
3842 auto names_3d_it = replacement_names_3d.end();
3845 names_3d_it = replacement_names_3d.find(index_key);
3847 if (names_3d_it == replacement_names_3d.end() || current_names_3d_it ==
program_names_3d.end()) {
3848 error =
"Shader reload is missing 3D uniform metadata";
3858 std::swap(current_names_2d_it->second, names_2d_it->second);
3861 std::swap(current_names_3d_it->second, names_3d_it->second);
3891 void setupProgramUniforms(gl::GLWindow *win, gl::ShaderProgram *prog, std::unordered_map<int, ProgramData> &names,
size_t pos,
const std::string &text) {
3892 GLenum error = glGetError();
3893 if (error != GL_NO_ERROR) {
3894 throw mx::Exception(
"OpenGL Error: on ShaderLibary::loadProgram: " + std::to_string(error));
3897 GLint loc = glGetUniformLocation(prog->id(),
"iResolution");
3898 glUniform2f(loc, win->w, win->h);
3899 error = glGetError();
3900 if (error != GL_NO_ERROR) {
3901 throw mx::Exception(
"setUniform");
3904 std::filesystem::path file_path(text);
3905 std::string name = file_path.stem().string();
3906 if (!name.empty()) {
3907 names[pos].name = name;
3909 glGetProgramiv(prog->id(), GL_COMPUTE_WORK_GROUP_SIZE, names[pos].compute_work_group_size);
3911 names[pos].loc = glGetUniformLocation(prog->id(),
"alpha");
3912 names[pos].iTime = glGetUniformLocation(prog->id(),
"iTime");
3913 names[pos].iMouse = glGetUniformLocation(prog->id(),
"iMouse");
3914 names[pos].time_f = glGetUniformLocation(prog->id(),
"time_f");
3915 names[pos].iResolution = glGetUniformLocation(prog->id(),
"iResolution");
3916 names[pos].iFrame = glGetUniformLocation(prog->id(),
"iFrame");
3917 names[pos].iTimeDelta = glGetUniformLocation(prog->id(),
"iTimeDelta");
3918 names[pos].iDate = glGetUniformLocation(prog->id(),
"iDate");
3919 names[pos].iFrameRate = glGetUniformLocation(prog->id(),
"iFrameRate");
3920 names[pos].iMouseClick = glGetUniformLocation(prog->id(),
"iMouseClick");
3922 for (
int i = 0; i < 4; ++i) {
3923 std::string channelTime =
"iChannelTime[" + std::to_string(i) +
"]";
3924 std::string channelRes =
"iChannelResolution[" + std::to_string(i) +
"]";
3925 names[pos].iChannelTime[i] = glGetUniformLocation(prog->id(), channelTime.c_str());
3926 names[pos].iChannelResolution[i] = glGetUniformLocation(prog->id(), channelRes.c_str());
3929 if (name.find(
"cache") != std::string::npos) {
3930 names[pos].history_loc = glGetUniformLocation(prog->id(),
"history");
3931 names[pos].history_head_loc = glGetUniformLocation(prog->id(),
"history_head");
3933 glUniform1i(names[pos].history_loc, 1);
3936 glUniform1i(names[pos].history_head_loc,
history_head);
3940 for (
int i = 0; i < 8; ++i) {
3941 names[pos].texture_cache_loc[i] = glGetUniformLocation(prog->id(), std::string(
"samp" + std::to_string(i + 1)).c_str());
3946 names[pos].texture_array_base_loc = glGetUniformLocation(prog->id(),
"textures[0]");
3948 std::vector<GLint> units(
static_cast<std::size_t
>(
cache_size), 0);
3950 units[
static_cast<std::size_t
>(i)] = i + 1;
3952 glUniform1iv(names[pos].texture_array_base_loc,
cache_size, units.data());
3958 names[pos].texture_array_loc.assign(
static_cast<std::size_t
>(
cache_size), -1);
3960 names[pos].texture_array_loc[i] = glGetUniformLocation(prog->id(), std::string(
"textures[" + std::to_string(i) +
"]").c_str());
3965 names[pos].amp = glGetUniformLocation(prog->id(),
"amp");
3966 names[pos].amp_untouched = glGetUniformLocation(prog->id(),
"uamp");
3967 names[pos].iamp = glGetUniformLocation(prog->id(),
"iamp");
3968 names[pos].amp_peak = glGetUniformLocation(prog->id(),
"amp_peak");
3969 names[pos].amp_rms = glGetUniformLocation(prog->id(),
"amp_rms");
3970 names[pos].amp_smooth = glGetUniformLocation(prog->id(),
"amp_smooth");
3971 names[pos].amp_low = glGetUniformLocation(prog->id(),
"amp_low");
3972 names[pos].amp_mid = glGetUniformLocation(prog->id(),
"amp_mid");
3973 names[pos].amp_high = glGetUniformLocation(prog->id(),
"amp_high");
3974 names[pos].iSampleRate = glGetUniformLocation(prog->id(),
"iSampleRate");
3975 names[pos].spectrum_loc = glGetUniformLocation(prog->id(),
"spectrum");
3976 names[pos].spectrum_zero_loc = glGetUniformLocation(prog->id(),
"spectrum0");
3977 names[pos].spectrum_history_loc = glGetUniformLocation(prog->id(),
"spectrum_history");
3978 names[pos].spectrum_history_head_loc = glGetUniformLocation(prog->id(),
"spectrum_history_head");
3979 names[pos].spectrum_history_size_loc = glGetUniformLocation(prog->id(),
"spectrum_history_size");
3982 names[pos].value_alpha_r = glGetUniformLocation(prog->id(),
"value_alpha_r");
3983 names[pos].value_alpha_g = glGetUniformLocation(prog->id(),
"value_alpha_g");
3984 names[pos].value_alpha_b = glGetUniformLocation(prog->id(),
"value_alpha_b");
3985 names[pos].alpha_r_loc = glGetUniformLocation(prog->id(),
"alpha_r");
3986 names[pos].alpha_g_loc = glGetUniformLocation(prog->id(),
"alpha_g");
3987 names[pos].alpha_b_loc = glGetUniformLocation(prog->id(),
"alpha_b");
3988 names[pos].alpha_value = glGetUniformLocation(prog->id(),
"alpha_value");
3989 names[pos].index_value = glGetUniformLocation(prog->id(),
"index_value");
3990 names[pos].optx_loc = glGetUniformLocation(prog->id(),
"optx");
3991 names[pos].random_var_loc = glGetUniformLocation(prog->id(),
"random_var");
3992 names[pos].restore_black_loc = glGetUniformLocation(prog->id(),
"restore_black");
3993 names[pos].inc_value_loc = glGetUniformLocation(prog->id(),
"inc_value");
3994 names[pos].inc_valuex_loc = glGetUniformLocation(prog->id(),
"inc_valuex");
3995 names[pos].time_speed_loc = glGetUniformLocation(prog->id(),
"time_speed");
3998 names[pos].custom_uniform_locs[i] = glGetUniformLocation(prog->id(),
custom_uniforms[i].name.c_str());
4001 names[pos].slider_loc[0] = glGetUniformLocation(prog->id(),
"slider1");
4002 names[pos].slider_loc[1] = glGetUniformLocation(prog->id(),
"slider2");
4003 names[pos].slider_loc[2] = glGetUniformLocation(prog->id(),
"slider3");
4004 names[pos].slider_loc[3] = glGetUniformLocation(prog->id(),
"slider4");
4015 auto it = names.find(
index());
4016 if (it == names.end())
4018 GLint loc = it->second.iFrameRate;
4020 glUniform1f(loc, fps_value);
4038 if (names.find(
index()) == names.end()) {
4042 if (value == 0 && names[
index()].history_loc != -1) {
4043 glUniform1i(names[
index()].history_loc, 1);
4049 glUniform1i(names[
index()].texture_cache_loc[value], value + 1);
4054 if (names[
index()].texture_array_base_loc != -1) {
4060 if (value <
static_cast<int>(names[
index()].texture_array_loc.size())) {
4061 glUniform1i(names[
index()].texture_array_loc[value], value + 1);
4108 if (std::isfinite(fps) && fps > 0.0)
4122 mx::system_out <<
"acmx2: Time speed: " <<
time_speed <<
"\n";
4138 mx::system_out <<
"acmx2: Time speed (decelerating): " <<
time_speed <<
"\n";
4167 mx::system_out <<
"acmx2: Cannot switch to 3D - dual mode not enabled\n";
4172 mx::system_out <<
"acmx2: Switched to " << (
is3d ?
"3D" :
"2D") <<
" mode\n";
4189 mx::system_out <<
"acmx2: Shader processing " << state <<
"\n";
4197 void loadPrograms(gl::GLWindow *win,
const std::string &text, mx::Font &loadingFont) {
4199 std::string manifest_error;
4201 throw mx::Exception(
"acmx2: " + manifest_error);
4204 std::vector<std::string> shader_files;
4205 for (
const std::string &entry : manifest.
entries) {
4209 std::string full_path;
4211 shader_files.push_back(*shader_entry);
4216 std::sort(shader_files.begin(), shader_files.end(), [](
const std::string &a,
const std::string &b) { return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end(), [](unsigned char ca, unsigned char cb) { return std::tolower(ca) < std::tolower(cb); }); });
4218 size_t total_shaders = shader_files.size();
4220 const char *load_action =
use_cache ?
"Loading" :
"Compiling";
4221 mx::system_out <<
"acmx2: " << load_action <<
" " << total_shaders <<
" shaders (" << (
dual_mode ?
"2D+3D" :
"2D") <<
")...\n";
4224 static constexpr const char *kLogoVert =
"#version 330 core\n"
4225 "layout(location = 0) in vec3 aPos;\n"
4226 "layout(location = 1) in vec2 aTex;\n"
4228 "void main() { gl_Position = vec4(aPos, 1.0); tc = aTex; }\n";
4229 static constexpr const char *kLogoFrag =
"#version 330 core\n"
4232 "uniform sampler2D samp;\n"
4233 "void main() { color = texture(samp, tc); }\n";
4235 gl::ShaderProgram logo_shader;
4236 auto logo_sprite = std::make_unique<gl::GLSprite>();
4237 bool logo_loaded =
false;
4239 std::string logo_path = win->util.getFilePath(
"data/logo.png");
4240 if (std::filesystem::exists(logo_path)) {
4241 GLuint logo_tex = 0;
4244 logo_tex = gl::loadTexture(logo_path, lw, lh);
4245 if (logo_tex && logo_shader.loadProgramFromText(kLogoVert, kLogoFrag)) {
4246 logo_sprite->initSize(win->w, win->h);
4247 logo_sprite->setName(
"samp");
4248 logo_sprite->setShader(&logo_shader);
4249 float scale = std::min(
static_cast<float>(win->w) / lw,
static_cast<float>(win->h) / lh);
4250 int dw =
static_cast<int>(lw * scale);
4251 int dh =
static_cast<int>(lh * scale);
4252 int lx = (win->w - dw) / 2;
4253 int ly = (win->h - dh) / 2;
4254 logo_sprite->initWithTexture(&logo_shader, logo_tex, lx, ly, dw, dh);
4261 glDeleteTextures(1, &logo_tex);
4266 int last_percent_reported = -1;
4267 for (
size_t shader_index = 0; shader_index < shader_files.size(); ++shader_index) {
4268 const std::string &line_data = shader_files[shader_index];
4269 std::string full_path = text +
"/" + line_data;
4270 std::string vert_2d = win->util.getFilePath(
"data/vert.glsl");
4271 std::string vert_3d = win->util.getFilePath(
"data/vertex.glsl");
4276 std::string compute_error;
4278 if (compute_program) {
4279 programs_2d.push_back(std::move(compute_program));
4282 mx::system_out <<
"acmx2: ⚠ " << compute_error <<
" — substituting passthrough placeholder\n";
4284 }
else if (!is_compute) {
4288 }
catch (
const std::exception &e) {
4289 mx::system_out <<
"acmx2: ⚠ Exception compiling 2D shader: " << line_data <<
" (" << e.what() <<
")\n";
4291 mx::system_out <<
"acmx2: ⚠ Unknown exception compiling 2D shader: " << line_data <<
"\n";
4299 throw mx::Exception(
"acmx2: Error could not build 2D passthrough placeholder for: " + line_data);
4302 mx::system_out <<
"acmx2: Skipping unsupported compute shader: " << line_data <<
"\n";
4311 throw mx::Exception(
"acmx2: Error could not build 3D compute passthrough for: " + line_data);
4324 throw mx::Exception(
"acmx2: Error could not build 3D passthrough placeholder for: " + line_data);
4331 int percent =
static_cast<int>((shader_index + 1) * 100 / total_shaders);
4332 int percent_bucket = (percent / 10) * 10;
4333 if (percent_bucket > last_percent_reported) {
4334 last_percent_reported = percent_bucket;
4335 mx::system_out <<
"acmx2: " << load_action <<
"... " << percent_bucket <<
"% (" << (shader_index + 1) <<
"/" << total_shaders <<
" shaders)\n";
4338 glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
4339 glClear(GL_COLOR_BUFFER_BIT);
4341 logo_sprite->draw();
4343 if (loadingFont.handle().has_value()) {
4344 std::string loadingText = std::string(load_action) +
" Shader " + std::to_string(shader_index + 1) +
"/" + std::to_string(total_shaders) +
"...";
4345 win->text.printText_Blended(loadingFont, 10, 10, loadingText);
4347 SDL_GL_SwapWindow(win->getWindow());
4351 mx::system_out <<
"acmx2: " << (
use_cache ?
"Loaded " :
"Compiled ") << shader_files.size() <<
" shaders (" << (
dual_mode ?
"2D+3D" :
"2D only") <<
")\n";
4377 bool removeBrokenShaders(gl::GLWindow *win,
const std::string &library_path,
const std::string &vert_2d,
const std::string &vert_3d) {
4378 static_cast<void>(win);
4379 if (glGetString(GL_VERSION) ==
nullptr) {
4380 mx::system_err <<
"acmx2: remove-broken requires a valid OpenGL context\n";
4385 std::string manifest_error;
4387 mx::system_err <<
"acmx2: " << manifest_error <<
"\n";
4391 const std::string manifest_path = manifest.
path;
4392 std::string crash_marker_path = library_path +
"/.remove_broken_last_shader";
4394 auto writeCrashMarker = [&](
const std::string &shader_name) {
4395 std::ofstream marker_out(crash_marker_path, std::ios::trunc);
4396 if (marker_out.is_open()) {
4397 marker_out << shader_name;
4402 auto clearCrashMarker = [&]() {
4403 std::error_code marker_ec;
4404 std::filesystem::remove(crash_marker_path, marker_ec);
4407 std::string last_crashed_shader;
4409 std::ifstream marker_in(crash_marker_path);
4410 if (marker_in.is_open()) {
4411 std::getline(marker_in, last_crashed_shader);
4421 std::vector<Line> lines;
4422 for (
const std::string &manifest_entry : manifest.
entries) {
4424 entry.raw = manifest_entry;
4426 std::string full_path;
4429 entry.raw = *shader_entry;
4430 entry.is_shader = is_shader_line;
4431 if (!is_shader_line && !manifest_entry.empty() && manifest_entry.find(
"material") == std::string::npos) {
4432 mx::system_out <<
"acmx2: ⚠ Removing missing file from manifest: " << manifest_entry <<
"\n";
4433 entry.is_shader =
false;
4436 lines.push_back(std::move(entry));
4439 size_t pre_removed_from_crash = 0;
4440 if (!last_crashed_shader.empty()) {
4441 for (
auto &entry : lines) {
4442 if (!entry.is_shader || !entry.keep) {
4445 if (entry.raw == last_crashed_shader) {
4447 ++pre_removed_from_crash;
4448 mx::system_out <<
"acmx2: ⚠ Previous scan crashed while compiling '" << last_crashed_shader <<
"' — removing it and resuming scan\n";
4454 size_t total_shaders = 0;
4455 for (
const auto &e : lines) {
4460 mx::system_out <<
"acmx2: Scanning " << total_shaders <<
" shaders in " << library_path <<
" for compile errors (" << (
dual_mode ?
"2D+3D" :
"2D only") <<
")\n";
4463 size_t removed = pre_removed_from_crash;
4466 for (
auto &entry : lines) {
4467 if (!entry.is_shader || !entry.keep)
4470 std::string full_path = library_path +
"/" + entry.raw;
4480 std::string scan_line =
"acmx2: [" + std::to_string(scanned) +
"/" + std::to_string(total_shaders) +
"] " + entry.raw +
" ...\n";
4481 mx::system_out << scan_line;
4482 mx::system_out.flush();
4486 bool compiled =
true;
4488 writeCrashMarker(entry.raw);
4489#if defined(__APPLE__)
4492 ScopedStderrSilence silence_stderr;
4496 std::string compute_error;
4498 if (!compiled && !compute_error.empty())
4499 mx::system_out << compute_error <<
" ";
4501 mx::system_out <<
"compute unavailable; kept without validation ";
4504 gl::ShaderProgram prog_2d;
4505 prog_2d.setSilent(
true);
4509 GLint link_status = 0;
4510 glGetProgramiv(prog_2d.id(), GL_LINK_STATUS, &link_status);
4511 if (link_status != GL_TRUE)
4515 gl::ShaderProgram prog_3d;
4516 prog_3d.setSilent(
true);
4520 GLint link_status = 0;
4521 glGetProgramiv(prog_3d.id(), GL_LINK_STATUS, &link_status);
4522 if (link_status != GL_TRUE)
4527 }
catch (
const std::exception &e) {
4528 mx::system_out <<
"exception: " << e.what() <<
" ";
4536 mx::system_out <<
"acmx2: -> OK\n";
4539 mx::system_out <<
"acmx2: -> REMOVED\n";
4543 mx::system_out.flush();
4551 std::filesystem::copy_file(manifest_path, manifest_path +
".bak", std::filesystem::copy_options::overwrite_existing, ec);
4553 mx::system_out <<
"acmx2: Warning: could not create manifest backup (" << ec.message() <<
")\n";
4556 std::vector<std::string> kept_entries;
4557 for (
const auto &entry : lines) {
4559 kept_entries.push_back(entry.raw);
4563 mx::system_err <<
"acmx2: " << manifest_error <<
"\n";
4567 std::ofstream out(manifest_path, std::ios::trunc);
4568 if (!out.is_open()) {
4569 mx::system_err <<
"acmx2: Could not rewrite manifest at: " << manifest_path <<
"\n";
4572 for (
const std::string &entry : kept_entries)
4573 out << entry <<
"\n";
4579 if (std::filesystem::exists(cache_file)) {
4580 std::filesystem::remove(cache_file, ec);
4582 mx::system_out <<
"acmx2: Removed stale shader cache: " << cache_file <<
"\n";
4586 mx::system_out <<
"acmx2: Remove-broken complete: kept " << kept <<
", removed " << removed <<
" shader(s). "
4587 <<
"Backup written to " << manifest_path <<
".bak\n";
4600 entry.
shader_name = std::filesystem::path(shader_file).stem().string();
4604 const auto markFailed = [&](
const std::string &reason) {
4610 mx::system_err <<
"acmx2: Incremental cache compile failed for " << shader_file <<
": " << reason <<
"\n";
4611 mx::system_err.flush();
4613 const auto extractBinary = [](GLuint program, std::vector<char> &
binary, GLenum &format) {
4614 GLint binaryLength = 0;
4615 glGetProgramiv(program, GL_PROGRAM_BINARY_LENGTH, &binaryLength);
4616 if (binaryLength <= 0)
4618 binary.resize(
static_cast<std::size_t
>(binaryLength));
4619 GLsizei actualLength = 0;
4621 while (glGetError() != GL_NO_ERROR) {
4624 if (glGetError() != GL_NO_ERROR || actualLength <= 0) {
4629 binary.resize(
static_cast<std::size_t
>(actualLength));
4637 markFailed(
"OpenGL 4.3 compute shaders are unavailable");
4641 std::string compute_error;
4643 if (!compute_program) {
4644 markFailed(compute_error);
4648 markFailed(
"could not extract compute program binary");
4655 gl::ShaderProgram program2d;
4656 program2d.setSilent(
true);
4658 markFailed(
"2D compile failed");
4662 markFailed(
"could not extract 2D program binary");
4667 gl::ShaderProgram program3d;
4668 program3d.setSilent(
true);
4670 markFailed(
"3D compile failed");
4674 markFailed(
"could not extract 3D program binary");
4678 }
catch (
const std::exception &error) {
4679 markFailed(error.what());
4682 markFailed(
"unknown exception");
4699 bool buildShaderCache(gl::GLWindow *win,
const std::string &library_path,
const std::string &vert_2d,
const std::string &vert_3d, mx::Font *loadingFont =
nullptr) {
4700 if (glGetString(GL_VERSION) ==
nullptr) {
4701 mx::system_err <<
"acmx2: build-cache requires a valid OpenGL context\n";
4705 GLint numFormats = 0;
4706 glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS, &numFormats);
4707 if (numFormats == 0) {
4708 mx::system_err <<
"acmx2: Error - OpenGL driver does not support program binaries\n";
4711 mx::system_out <<
"acmx2: OpenGL supports " << numFormats <<
" program binary format(s)\n";
4715 mx::system_err <<
"acmx2: Error - Failed to load program binary extension functions\n";
4718 mx::system_out <<
"acmx2: Program binary functions loaded successfully\n";
4723 std::string manifest_error;
4725 mx::system_err <<
"acmx2: " << manifest_error <<
"\n";
4729 std::vector<std::string> shader_files;
4731 mx::system_err <<
"acmx2: " << manifest_error <<
"\n";
4740 mx::system_out <<
"acmx2: Building shader cache for " << shader_files.size() <<
" shaders...\n";
4743 static constexpr const char *kCacheLogoVert =
"#version 330 core\n"
4744 "layout(location = 0) in vec3 aPos;\n"
4745 "layout(location = 1) in vec2 aTex;\n"
4747 "void main() { gl_Position = vec4(aPos, 1.0); tc = aTex; }\n";
4748 static constexpr const char *kCacheLogoFrag =
"#version 330 core\n"
4751 "uniform sampler2D samp;\n"
4752 "void main() { color = texture(samp, tc); }\n";
4754 gl::ShaderProgram cache_logo_shader;
4755 auto cache_logo_sprite = std::make_unique<gl::GLSprite>();
4756 bool cache_logo_loaded =
false;
4757 if (win !=
nullptr) {
4758 const std::string logo_path = win->util.getFilePath(
"data/logo.png");
4759 if (std::filesystem::exists(logo_path)) {
4760 GLuint logo_texture = 0;
4763 int logo_height = 0;
4764 logo_texture = gl::loadTexture(logo_path, logo_width, logo_height);
4765 if (logo_texture && cache_logo_shader.loadProgramFromText(kCacheLogoVert, kCacheLogoFrag)) {
4766 cache_logo_sprite->initSize(win->w, win->h);
4767 cache_logo_sprite->setName(
"samp");
4768 cache_logo_sprite->setShader(&cache_logo_shader);
4769 const float scale = std::min(
static_cast<float>(win->w) / logo_width,
static_cast<float>(win->h) / logo_height);
4770 const int draw_width =
static_cast<int>(logo_width * scale);
4771 const int draw_height =
static_cast<int>(logo_height * scale);
4772 const int draw_x = (win->w - draw_width) / 2;
4773 const int draw_y = (win->h - draw_height) / 2;
4774 cache_logo_sprite->initWithTexture(&cache_logo_shader, logo_texture, draw_x, draw_y, draw_width, draw_height);
4776 cache_logo_loaded =
true;
4781 glDeleteTextures(1, &logo_texture);
4785 int last_progress_percent = -1;
4786 const auto present_cache_progress = [&](std::size_t completed) {
4793 const int percent = shader_files.empty() ? 100 :
static_cast<int>(completed * 100 / shader_files.size());
4794 if (percent == last_progress_percent)
4796 last_progress_percent = percent;
4798 glViewport(0, 0, win->w, win->h);
4799 glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
4800 glClear(GL_COLOR_BUFFER_BIT);
4801 if (cache_logo_loaded)
4802 cache_logo_sprite->draw();
4803 if (loadingFont !=
nullptr && loadingFont->handle().has_value()) {
4804 const std::string progress_text =
"Building Shader Cache " + std::to_string(completed) +
"/" + std::to_string(shader_files.size()) +
"...";
4805 win->text.printText_Blended(*loadingFont, 10, 10, progress_text);
4807 SDL_GL_SwapWindow(win->getWindow());
4811 present_cache_progress(0);
4813 for (
size_t i = 0; i < shader_files.size(); ++i) {
4814 present_cache_progress(i);
4815 const std::string &shader_file = shader_files[i];
4816 std::string full_path = library_path +
"/" + shader_file;
4818 mx::system_out <<
"acmx2: Caching Shader " << i <<
"/" << shader_files.size() <<
": [" << shader_file <<
"] \n";
4822 std::filesystem::path file_path(shader_file);
4825 mx::system_out <<
" - Computing hash... ";
4828 mx::system_out <<
"done\n";
4833 cache.
entries.push_back(std::move(entry));
4834 mx::system_out << (compiled ?
" ✔ COMPUTE SUCCESS\n" :
" ⚠ COMPUTE SKIPPED (passthrough placeholder)\n");
4839 auto mark_failed = [&](
const char *reason) {
4845 mx::system_out <<
" ⚠ SKIPPED: " << reason <<
" (slot preserved; will run as passthrough)\n";
4847 cache.
entries.push_back(std::move(entry));
4851 mx::system_out <<
" - Compiling 2D shader... ";
4854 gl::ShaderProgram prog_2d;
4855 prog_2d.setSilent(
true);
4857 mx::system_out <<
" ❌ (2D compile failed)\n";
4859 mark_failed(
"2D compile failed");
4862 mx::system_out <<
"done (id=" << prog_2d.id() <<
")\n";
4865 GLint link_status = 0;
4866 glGetProgramiv(prog_2d.id(), GL_LINK_STATUS, &link_status);
4867 if (link_status != GL_TRUE) {
4868 mx::system_out <<
" - ❌ Program not properly linked\n";
4870 mark_failed(
"2D link failed");
4874 GLint binary_retrievable = 0;
4875 glGetProgramiv(prog_2d.id(), GL_PROGRAM_BINARY_RETRIEVABLE_HINT, &binary_retrievable);
4876 mx::system_out <<
" - Binary retrievable hint: " << binary_retrievable <<
"\n";
4879 mx::system_out <<
" - Getting binary length... ";
4882 GLint binary_length = 0;
4883 glGetProgramiv(prog_2d.id(), GL_PROGRAM_BINARY_LENGTH, &binary_length);
4884 GLenum gl_error = glGetError();
4886 mx::system_out << binary_length <<
" bytes\n";
4889 if (gl_error != GL_NO_ERROR) {
4890 mx::system_out <<
" ❌ (GL error: " << gl_error <<
")\n";
4892 mark_failed(
"GL error retrieving 2D binary length");
4896 if (binary_length > 0) {
4897 mx::system_out <<
" - Extracting binary... ";
4900 void *binary_buffer = malloc(binary_length);
4901 if (!binary_buffer) {
4902 mx::system_out <<
"❌ (malloc failed)\n";
4904 mark_failed(
"malloc failed for 2D binary");
4908 GLsizei actual_length = 0;
4911 mx::system_out <<
"calling glGetProgramBinary... ";
4915 gl_error = glGetError();
4917 mx::system_out <<
"done. actual=" << actual_length <<
", format=" << format <<
"\n";
4920 if (gl_error != GL_NO_ERROR || actual_length == 0) {
4921 mx::system_out <<
" ❌ (binary extraction failed, gl_error=" << gl_error <<
")\n";
4923 free(binary_buffer);
4924 mark_failed(
"2D binary extraction failed");
4929 memcpy(entry.
binary_2d.data(), binary_buffer, actual_length);
4931 free(binary_buffer);
4933 mx::system_out <<
" ❌ (no binary available)\n";
4935 mark_failed(
"no 2D binary available");
4940 mx::system_out <<
" - Compiling 3D shader... ";
4943 gl::ShaderProgram prog_3d;
4944 prog_3d.setSilent(
true);
4946 mx::system_out <<
" ❌ (3D compile failed)\n";
4948 mark_failed(
"3D compile failed");
4952 mx::system_out <<
"done (id=" << prog_3d.id() <<
")\n";
4955 mx::system_out <<
" - Getting 3D binary... ";
4958 GLint binary_length_3d = 0;
4959 glGetProgramiv(prog_3d.id(), GL_PROGRAM_BINARY_LENGTH, &binary_length_3d);
4961 mx::system_out << binary_length_3d <<
" bytes\n";
4964 if (binary_length_3d > 0) {
4965 entry.
binary_3d.resize(binary_length_3d);
4966 GLsizei actual_length_3d = 0;
4968 entry.
binary_3d.resize(actual_length_3d);
4969 mx::system_out <<
" - 3D binary extracted: " << actual_length_3d <<
" bytes\n";
4974 cache.
entries.push_back(std::move(entry));
4975 mx::system_out <<
" ✔ SUCCESS\n";
4977 }
catch (
const std::exception &e) {
4978 mx::system_out <<
" ❌ (exception: " << e.what() <<
")\n";
4980 mark_failed(
"exception during compile");
4983 mx::system_out <<
" ❌ (unknown exception)\n";
4985 mark_failed(
"unknown exception during compile");
4990 present_cache_progress(shader_files.size());
4992 if (cache.
save(cache_file)) {
4993 size_t ok_count = 0;
4994 size_t failed_count = 0;
4995 for (
const auto &e : cache.
entries) {
5001 mx::system_out <<
"acmx2: Shader cache saved to: " << cache_file <<
"\n";
5002 mx::system_out <<
"acmx2: Cached " << ok_count <<
" shaders (" << (
dual_mode ?
"2D+3D" :
"2D only") <<
"), " << failed_count <<
" failed (passthrough placeholders)\n";
5006 mx::system_err <<
"acmx2: Failed to save shader cache to: " << cache_file <<
"\n";
5035 if (names.find(idx) != names.end()) {
5036 return names[idx].name;
5055 if (!pass_list.empty()) {
5057 for (
size_t i = 0; i < pass_list.size(); ++i) {
5058 int idx = pass_list[i];
5059 if (names.find(idx) != names.end()) {
5060 name += names[idx].name;
5062 name += std::to_string(idx);
5064 if (i + 1 < pass_list.size()) {
5074 bool loadFromCache(gl::GLWindow *win,
const std::string &library_path, mx::Font &loadingFont,
const std::string &vert_2d =
"",
const std::string &vert_3d =
"") {
5076 std::string manifest_error;
5078 mx::system_out <<
"acmx2: " << manifest_error <<
"\n";
5084 mx::system_out <<
"acmx2: Checking for shader cache at: " << cache_file <<
"\n";
5087 if (!std::filesystem::exists(cache_file)) {
5088 mx::system_out <<
"acmx2: No shader cache found, will compile shaders\n";
5093 mx::system_out <<
"acmx2: Found shader cache, loading...\n";
5097 if (!cache.
load(cache_file)) {
5098 mx::system_out <<
"acmx2: Shader cache corrupted or incompatible, will recompile\n";
5103 mx::system_out <<
"acmx2: Could not load program binary extension, will recompile\n";
5107 std::string current_renderer =
safeGLString(GL_RENDERER);
5108 std::string current_version =
safeGLString(GL_VERSION);
5111 mx::system_out <<
"acmx2: GPU changed (was: " << cache.
gl_renderer <<
", now: " << current_renderer <<
"), will recompile\n";
5116 mx::system_out <<
"acmx2: Driver version changed (was: " << cache.
gl_version <<
", now: " << current_version <<
"), will recompile\n";
5121 mx::system_out <<
"acmx2: Cache was built in 2D-only mode but 3D is enabled, will rebuild with 2D+3D\n";
5125 std::vector<std::string> shader_files;
5127 mx::system_out <<
"acmx2: " << manifest_error <<
"\n";
5131 if (shader_files.size() != cache.
entries.size()) {
5132 mx::system_out <<
"acmx2: Shader count mismatch: manifest has " << shader_files.size() <<
" shaders but cache has " << cache.
entries.size() <<
" entries. Rebuilding cache...\n";
5137 std::vector<std::size_t> staleIndices;
5138 for (std::size_t i = 0; i < shader_files.size(); ++i) {
5139 const std::string fullPath = library_path +
"/" + shader_files[i];
5142 if (currentHash != cache.
entries[i].source_hash || cache.
entries[i].shader_name != std::filesystem::path(shader_files[i]).stem().string() || cache.
entries[i].kind != expectedKind) {
5143 staleIndices.push_back(i);
5147 if (!staleIndices.empty()) {
5148 mx::system_out <<
"acmx2: Updating " << staleIndices.size() <<
" changed shader cache entr" << (staleIndices.size() == 1 ?
"y" :
"ies") <<
"...\n";
5149 mx::system_out.flush();
5150 for (
const std::size_t
index : staleIndices) {
5151 const std::string fullPath = library_path +
"/" + shader_files[
index];
5152 mx::system_out <<
"acmx2: Incremental cache update: " << shader_files[
index] <<
"\n";
5153 mx::system_out.flush();
5156 if (!cache.
save(cache_file)) {
5157 mx::system_err <<
"acmx2: Could not save incrementally updated shader cache\n";
5162 mx::system_out <<
"acmx2: Loading " << cache.
entries.size() <<
" shaders from cache...\n";
5166 static constexpr const char *kLogoVertC =
"#version 330 core\n"
5167 "layout(location = 0) in vec3 aPos;\n"
5168 "layout(location = 1) in vec2 aTex;\n"
5170 "void main() { gl_Position = vec4(aPos, 1.0); tc = aTex; }\n";
5171 static constexpr const char *kLogoFragC =
"#version 330 core\n"
5174 "uniform sampler2D samp;\n"
5175 "void main() { color = texture(samp, tc); }\n";
5177 gl::ShaderProgram logo_shader_c;
5178 auto logo_sprite_c = std::make_unique<gl::GLSprite>();
5179 bool logo_loaded_c =
false;
5181 std::string logo_path = win ? win->util.getFilePath(
"data/logo.png") : std::string();
5182 if (!logo_path.empty() && std::filesystem::exists(logo_path)) {
5183 GLuint logo_tex = 0;
5186 logo_tex = gl::loadTexture(logo_path, lw, lh);
5187 if (logo_tex && logo_shader_c.loadProgramFromText(kLogoVertC, kLogoFragC)) {
5188 logo_sprite_c->initSize(win->w, win->h);
5189 logo_sprite_c->setName(
"samp");
5190 logo_sprite_c->setShader(&logo_shader_c);
5191 float scale = std::min(
static_cast<float>(win->w) / lw,
static_cast<float>(win->h) / lh);
5192 int dw =
static_cast<int>(lw * scale);
5193 int dh =
static_cast<int>(lh * scale);
5194 int lx = (win->w - dw) / 2;
5195 int ly = (win->h - dh) / 2;
5196 logo_sprite_c->initWithTexture(&logo_shader_c, logo_tex, lx, ly, dw, dh);
5198 logo_loaded_c =
true;
5203 glDeleteTextures(1, &logo_tex);
5208 int last_percent_reported = -1;
5209 size_t binary_fail_count = 0;
5213 auto push_passthrough_2d = [&](
size_t i,
const char *reason) {
5216 mx::system_err <<
"acmx2: ❌ Failed to build 2D passthrough for slot " << i <<
" [" << (i < shader_files.size() ? shader_files[i] : std::string(
"?")) <<
"]\n";
5219 mx::system_out <<
"acmx2: ⚠ Slot " << i <<
" [" << (i < shader_files.size() ? shader_files[i] : std::string(
"?")) <<
"] using passthrough (" << reason <<
")\n";
5225 auto push_passthrough_3d = [&](
size_t i,
const char *reason) {
5228 mx::system_err <<
"acmx2: ❌ Failed to build 3D passthrough for slot " << i <<
" [" << (i < shader_files.size() ? shader_files[i] : std::string(
"?")) <<
"]\n";
5231 mx::system_out <<
"acmx2: ⚠ Slot " << i <<
" [" << (i < shader_files.size() ? shader_files[i] : std::string(
"?")) <<
"] using 3D passthrough (" << reason <<
")\n";
5238 for (
size_t i = 0; i < cache.
entries.size(); ++i) {
5239 const auto &entry = cache.
entries[i];
5245 if (entry.failed || entry.binary_2d.empty()) {
5246 if (!push_passthrough_2d(i, entry.failed ?
"cached as failed" :
"missing 2D binary")) {
5250 if (!push_passthrough_3d(i, entry.failed ?
"cached as failed" :
"missing 3D binary")) {
5258 GLuint prog_id_2d = glCreateProgram();
5260 GLenum gl_err = glGetError();
5261 glProgramBinaryFunc(prog_id_2d, entry.format_2d, entry.binary_2d.data(),
static_cast<GLsizei
>(entry.binary_2d.size()));
5262 gl_err = glGetError();
5264 GLint link_status = 0;
5265 glGetProgramiv(prog_id_2d, GL_LINK_STATUS, &link_status);
5266 if (link_status != GL_TRUE) {
5267 GLchar info_log[512];
5268 glGetProgramInfoLog(prog_id_2d, 512,
nullptr, info_log);
5269 mx::system_out <<
"acmx2: ❌ Shader " << i <<
" [" << entry.shader_name <<
"] 2D binary load failed, gl_err=" << gl_err <<
", format=" << entry.format_2d <<
", size=" << entry.binary_2d.size() <<
", log=" << info_log <<
"\n";
5271 glDeleteProgram(prog_id_2d);
5273 ++binary_fail_count;
5277 if (!push_passthrough_2d(i,
"2D binary load failed")) {
5281 if (!push_passthrough_3d(i,
"2D binary load failed")) {
5288 *
programs_2d.back() = gl::ShaderProgram(prog_id_2d);
5292 if (entry.binary_3d.empty()) {
5293 if (!push_passthrough_3d(i,
"missing 3D binary")) {
5298 GLuint prog_id_3d = glCreateProgram();
5299 glProgramBinaryFunc(prog_id_3d, entry.format_3d, entry.binary_3d.data(),
static_cast<GLsizei
>(entry.binary_3d.size()));
5301 glGetProgramiv(prog_id_3d, GL_LINK_STATUS, &link_status);
5302 if (link_status != GL_TRUE) {
5303 mx::system_out <<
"acmx2: ❌ Shader " << i <<
" [" << entry.shader_name <<
"] 3D binary load failed\n";
5305 glDeleteProgram(prog_id_3d);
5307 if (!push_passthrough_3d(i,
"3D binary load failed")) {
5311 *
programs_3d.back() = gl::ShaderProgram(prog_id_3d);
5317 int percent =
static_cast<int>((i + 1) * 100 / cache.
entries.size());
5318 int percent_bucket = (percent / 10) * 10;
5319 if (percent_bucket > last_percent_reported) {
5320 last_percent_reported = percent_bucket;
5321 mx::system_out <<
"acmx2: Cache loading... " << percent_bucket <<
"% (" << (i + 1) <<
"/" << cache.
entries.size() <<
" shaders)\n";
5324 glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
5325 glClear(GL_COLOR_BUFFER_BIT);
5326 if (logo_loaded_c) {
5327 logo_sprite_c->draw();
5329 if (loadingFont.handle().has_value()) {
5330 std::string loadingText =
"Loading Cached Shader " + std::to_string(i + 1) +
"/" + std::to_string(cache.
entries.size()) +
"...";
5331 win->text.printText_Blended(loadingFont, 10, 10, loadingText);
5333 SDL_GL_SwapWindow(win->getWindow());
5338 mx::system_out <<
"acmx2: Loaded " << cache.
entries.size() <<
" shaders from cache (" << (
dual_mode ?
"2D+3D" :
"2D only") <<
")\n";
5343 if (binary_fail_count > 0 && cache.
entries.size() > 0) {
5344 size_t fail_pct = (binary_fail_count * 100) / cache.
entries.size();
5345 if (fail_pct >= 10) {
5346 mx::system_out <<
"acmx2: ⚠ " << binary_fail_count <<
"/" << cache.
entries.size() <<
" cached shaders failed to load (" << fail_pct <<
"%) — cache is stale.\n";
5348 std::error_code rm_ec;
5349 std::filesystem::remove(cache_file, rm_ec);
5374 std::string manifest_error;
5376 throw mx::Exception(
"acmx2: " + manifest_error);
5378 std::string vert_2d = win->util.getFilePath(
"data/vert.glsl");
5379 std::string vert_3d = win->util.getFilePath(
"data/vertex.glsl");
5380 if (
loadFromCache(win, text, loadingFont, vert_2d, vert_3d)) {
5388 mx::system_out <<
"acmx2: Building shader cache at: " << cache_file <<
"\n";
5398 mx::system_out <<
"acmx2: Cache build/reload failed; compiling from source.\n";
5429 return it->second.name.find(
"cache") != std::string::npos;
5443 bool dispatchCompute2D(gl::GLWindow *win,
size_t idx, GLuint input_texture, GLuint output_texture) {
5448 gl::ShaderProgram *program =
programs_2d[idx].get();
5449 while (glGetError() != GL_NO_ERROR) {
5451 program->useProgram();
5453 glActiveTexture(GL_TEXTURE0);
5454 glBindTexture(GL_TEXTURE_2D, input_texture);
5455 const GLint input_location = glGetUniformLocation(program->id(),
"samp");
5456 if (input_location != -1)
5457 glUniform1i(input_location, 0);
5459 static constexpr const char *OUTPUT_IMAGE_NAMES[] = {
"outputImage",
"output_image",
"destTex",
"img_output"};
5460 for (
const char *name : OUTPUT_IMAGE_NAMES) {
5461 const GLint location = glGetUniformLocation(program->id(), name);
5463 glUniform1i(location, 0);
5465 glBindImageTexture(0, output_texture, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA16F);
5470 const GLuint groups_x = (
static_cast<GLuint
>(win->w) + local_x - 1) / local_x;
5471 const GLuint groups_y = (
static_cast<GLuint
>(win->h) + local_y - 1) / local_y;
5472 glDispatchCompute(groups_x, groups_y, 1);
5473 glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL_TEXTURE_FETCH_BARRIER_BIT | GL_FRAMEBUFFER_BARRIER_BIT);
5474 glBindImageTexture(0, 0, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA16F);
5475 return glGetError() == GL_NO_ERROR;
5489 if (i < progs.size()) {
5491 mx::system_out <<
"acmx2: Set Shader to Index: " << i <<
" [" << names[i].name <<
"] (" << (
is3d ?
"3D" :
"2D") <<
")\n";
5516 for (
auto &[idx, data] : names) {
5517 if (data.name == name)
5518 return static_cast<int>(idx);
5533 progs[
index()]->useProgram();
5538 return progs[
index()].get();
5548 if (idx < progs.size())
5549 return progs[idx].get();
5589 if (idx >= progs.size())
5591 if (names.find(idx) == names.end())
5594 static Uint64 start_time = SDL_GetPerformanceCounter();
5595 static Uint64 last_frame_time = start_time;
5596 static uint64_t frame_counter = 0;
5597 Uint64 now_time = SDL_GetPerformanceCounter();
5598 double elapsed_time =
static_cast<double>(now_time - start_time) / SDL_GetPerformanceFrequency();
5599 double delta_time =
static_cast<double>(now_time - last_frame_time) / SDL_GetPerformanceFrequency();
5600 last_frame_time = now_time;
5602 auto &n = names[idx];
5603 progs[idx]->useProgram();
5604 glUniform1f(n.loc,
alpha);
5605 glUniform1f(n.iTime,
static_cast<float>(elapsed_time));
5606 glUniform1f(n.time_f,
time_f);
5607 glUniform1i(n.iFrame,
static_cast<int>(frame_counter % INT_MAX));
5608 glUniform1f(n.iTimeDelta,
static_cast<float>(delta_time));
5609 auto now = std::chrono::system_clock::now();
5610 std::time_t now_c = std::chrono::system_clock::to_time_t(now);
5611 std::tm localTime_buf{};
5613 localtime_s(&localTime_buf, &now_c);
5615 localtime_r(&now_c, &localTime_buf);
5617 float year =
static_cast<float>(localTime_buf.tm_year + 1900);
5618 float month =
static_cast<float>(localTime_buf.tm_mon + 1);
5619 float day =
static_cast<float>(localTime_buf.tm_mday);
5620 float seconds =
static_cast<float>(localTime_buf.tm_hour * 3600 + localTime_buf.tm_min * 60 + localTime_buf.tm_sec);
5621 glUniform4f(n.iDate, year, month, day, seconds);
5622 if (n.iFrameRate != -1) {
5623 glUniform1f(n.iFrameRate, 24.0f);
5625 int mouseX = 0, mouseY = 0;
5626 Uint32 mouseState = SDL_GetMouseState(&mouseX, &mouseY);
5627 float currentY =
static_cast<float>(win->h - mouseY);
5628 float currentX =
static_cast<float>(mouseX);
5630 if (mouseState & SDL_BUTTON(SDL_BUTTON_LEFT)) {
5642 if (mouseState & SDL_BUTTON(SDL_BUTTON_RIGHT)) {
5651 glUniform4f(n.iMouse, currentX, currentY, leftClickFlag, rightClickFlag);
5655 glUniform2f(n.iResolution,
static_cast<float>(win->w),
static_cast<float>(win->h));
5656 if (n.time_speed_loc != -1) {
5665 glUniform1f(n.amp_untouched, audio_sensitivity);
5667 if (n.iSampleRate != -1) {
5669 glUniform1f(n.iSampleRate, sample_rate);
5672 glUniform1f(n.iamp, audio_metrics.frequency);
5676 if (n.amp_peak != -1) {
5677 glUniform1f(n.amp_peak, std::sqrt(audio_metrics.peak) * sense);
5679 if (n.amp_rms != -1) {
5680 glUniform1f(n.amp_rms, std::sqrt(audio_metrics.rms) * sense);
5682 if (n.amp_smooth != -1) {
5683 glUniform1f(n.amp_smooth, std::sqrt(audio_metrics.smooth) * sense);
5685 if (n.amp_low != -1) {
5686 glUniform1f(n.amp_low, std::sqrt(audio_metrics.low) * sense);
5688 if (n.amp_mid != -1) {
5689 glUniform1f(n.amp_mid, std::sqrt(audio_metrics.mid) * sense);
5691 if (n.amp_high != -1) {
5692 glUniform1f(n.amp_high, std::sqrt(audio_metrics.high) * sense);
5696 if (n.spectrum_loc != -1) {
5699 if (n.spectrum_zero_loc != -1)
5701 if (n.spectrum_history_loc != -1)
5703 if (n.spectrum_history_head_loc != -1)
5705 if (n.spectrum_history_size_loc != -1)
5710 if (n.history_loc != -1)
5711 glUniform1i(n.history_loc, 1);
5712 if (n.history_head_loc != -1)
5715 if (n.texture_array_base_loc != -1) {
5716 std::vector<GLint> units(
static_cast<std::size_t
>(
cache_size), 0);
5718 units[
static_cast<std::size_t
>(i)] = i + 1;
5720 glUniform1iv(n.texture_array_base_loc,
cache_size, units.data());
5722 for (
int i = 0; i < static_cast<int>(n.texture_array_loc.size()); ++i) {
5723 if (n.texture_array_loc[i] != -1)
5724 glUniform1i(n.texture_array_loc[i], i + 1);
5727 for (
int i = 0; i < 8 && i <
cache_size; ++i) {
5728 if (n.texture_cache_loc[i] != -1)
5729 glUniform1i(n.texture_cache_loc[i], i + 1);
5733 for (
int i = 0; i < 4; ++i) {
5734 if (n.slider_loc[i] != -1)
5759 static Uint64 start_time = SDL_GetPerformanceCounter();
5760 static Uint64 last_frame_time = start_time;
5761 static uint64_t frame_counter = 0;
5762 Uint64 now_time = SDL_GetPerformanceCounter();
5763 double elapsed_time =
static_cast<double>(now_time - start_time) / SDL_GetPerformanceFrequency();
5764 double delta_time =
static_cast<double>(now_time - last_frame_time) / SDL_GetPerformanceFrequency();
5765 last_frame_time = now_time;
5769 glUniform1f(n.loc,
alpha);
5770 glUniform1f(n.iTime,
static_cast<float>(elapsed_time));
5771 glUniform1f(n.time_f,
time_f);
5772 glUniform1i(n.iFrame,
static_cast<int>(frame_counter % INT_MAX));
5773 glUniform1f(n.iTimeDelta,
static_cast<float>(delta_time));
5774 auto now = std::chrono::system_clock::now();
5775 std::time_t now_c = std::chrono::system_clock::to_time_t(now);
5776 std::tm localTime_buf{};
5778 localtime_s(&localTime_buf, &now_c);
5780 localtime_r(&now_c, &localTime_buf);
5782 float year =
static_cast<float>(localTime_buf.tm_year + 1900);
5783 float month =
static_cast<float>(localTime_buf.tm_mon + 1);
5784 float day =
static_cast<float>(localTime_buf.tm_mday);
5785 float seconds =
static_cast<float>(localTime_buf.tm_hour * 3600 + localTime_buf.tm_min * 60 + localTime_buf.tm_sec);
5786 glUniform4f(n.iDate, year, month, day, seconds);
5787 if (n.iFrameRate != -1) {
5788 glUniform1f(n.iFrameRate, 24.0f);
5790 int mouseX = 0, mouseY = 0;
5791 Uint32 mouseState = SDL_GetMouseState(&mouseX, &mouseY);
5792 float currentY =
static_cast<float>(win->h - mouseY);
5793 float currentX =
static_cast<float>(mouseX);
5794 if (mouseState & SDL_BUTTON(SDL_BUTTON_LEFT)) {
5806 if (mouseState & SDL_BUTTON(SDL_BUTTON_RIGHT)) {
5815 glUniform4f(n.iMouse, currentX, currentY, leftClickFlag, rightClickFlag);
5819 glUniform2f(n.iResolution,
static_cast<float>(win->w),
static_cast<float>(win->h));
5820 if (n.time_speed_loc != -1) {
5829 glUniform1f(n.amp_untouched, audio_sensitivity);
5831 if (n.iSampleRate != -1) {
5833 glUniform1f(n.iSampleRate, sample_rate);
5836 glUniform1f(n.iamp, audio_metrics.frequency);
5840 if (n.amp_peak != -1) {
5841 glUniform1f(n.amp_peak, std::sqrt(audio_metrics.peak) * sense);
5843 if (n.amp_rms != -1) {
5844 glUniform1f(n.amp_rms, std::sqrt(audio_metrics.rms) * sense);
5846 if (n.amp_smooth != -1) {
5847 glUniform1f(n.amp_smooth, std::sqrt(audio_metrics.smooth) * sense);
5849 if (n.amp_low != -1) {
5850 glUniform1f(n.amp_low, std::sqrt(audio_metrics.low) * sense);
5852 if (n.amp_mid != -1) {
5853 glUniform1f(n.amp_mid, std::sqrt(audio_metrics.mid) * sense);
5855 if (n.amp_high != -1) {
5856 glUniform1f(n.amp_high, std::sqrt(audio_metrics.high) * sense);
5859 if (n.spectrum_loc != -1) {
5862 if (n.spectrum_zero_loc != -1)
5864 if (n.spectrum_history_loc != -1)
5866 if (n.spectrum_history_head_loc != -1)
5868 if (n.spectrum_history_size_loc != -1)
5873 if (n.history_loc != -1)
5874 glUniform1i(n.history_loc, 1);
5875 if (n.history_head_loc != -1)
5878 if (n.texture_array_base_loc != -1) {
5879 std::vector<GLint> units(
static_cast<std::size_t
>(
cache_size), 0);
5881 units[
static_cast<std::size_t
>(i)] = i + 1;
5883 glUniform1iv(n.texture_array_base_loc,
cache_size, units.data());
5885 for (
int i = 0; i < static_cast<int>(n.texture_array_loc.size()); ++i) {
5886 if (n.texture_array_loc[i] != -1)
5887 glUniform1i(n.texture_array_loc[i], i + 1);
5890 for (
int i = 0; i < 8 && i <
cache_size; ++i) {
5891 if (n.texture_cache_loc[i] != -1)
5892 glUniform1i(n.texture_cache_loc[i], i + 1);
5896 for (
int i = 0; i < 4; ++i) {
5897 if (n.slider_loc[i] != -1)
5922 if (names.find(
index()) == names.end()) {
5925 static Uint64 start_time = SDL_GetPerformanceCounter();
5926 static Uint64 last_frame_time = start_time;
5927 static uint64_t frame_counter = 0;
5928 static double last_good_delta = 1.0 / 60.0;
5930 Uint64 now_time = SDL_GetPerformanceCounter();
5932 double delta_time =
static_cast<double>(now_time - last_frame_time) / SDL_GetPerformanceFrequency();
5933 if (delta_time <= 0.0)
5934 delta_time = last_good_delta;
5936 last_good_delta = delta_time;
5937 last_frame_time = now_time;
5942 const float step =
static_cast<float>(time_delta) *
time_speed;
5948 float dt_scalex =
audio_delta ?
static_cast<float>(time_delta) : 1.0f;
5951 float new_ampx = audio_metrics.amplitude * audio_sensitivity *
time_speed * dt_scalex;
5961 constexpr float TIME_F_WRAP = 65536.0f * 6.2831853f;
5962 if (
time_f >= TIME_F_WRAP)
5970 GLuint time_f_loc = names[
index()].time_f;
5971 glUniform1f(time_f_loc,
time_f);
5972 GLint loc = names[
index()].loc;
5973 glUniform1f(loc,
alpha);
5974 GLint iTimeLoc = names[
index()].iTime;
5975 double currentTime =
static_cast<double>(SDL_GetTicks64()) / 1000.0f;
5976 glUniform1f(iTimeLoc, currentTime);
5977 GLint iFrameLoc = names[
index()].iFrame;
5978 glUniform1i(iFrameLoc,
static_cast<int>(frame_counter % INT_MAX));
5979 GLint iTimeDeltaLoc = names[
index()].iTimeDelta;
5980 glUniform1f(iTimeDeltaLoc,
static_cast<float>(delta_time));
5981 GLint iDateLoc = names[
index()].iDate;
5982 auto now = std::chrono::system_clock::now();
5983 std::time_t now_c = std::chrono::system_clock::to_time_t(now);
5984 std::tm localTime_buf{};
5986 localtime_s(&localTime_buf, &now_c);
5988 localtime_r(&now_c, &localTime_buf);
5990 float year =
static_cast<float>(localTime_buf.tm_year + 1900);
5991 float month =
static_cast<float>(localTime_buf.tm_mon + 1);
5992 float day =
static_cast<float>(localTime_buf.tm_mday);
5993 float seconds =
static_cast<float>(localTime_buf.tm_hour * 3600 + localTime_buf.tm_min * 60 + localTime_buf.tm_sec);
5994 glUniform4f(iDateLoc, year, month, day, seconds);
5996 GLint iFrameRateLoc = names[
index()].iFrameRate;
5997 if (iFrameRateLoc != -1) {
5998 glUniform1f(iFrameRateLoc, 24.0f);
6001 GLint iMouseLoc = names[
index()].iMouse;
6002 GLint iMouseClickLoc = names[
index()].iMouseClick;
6004 int mouseX = 0, mouseY = 0;
6005 Uint32 mouseState = SDL_GetMouseState(&mouseX, &mouseY);
6006 float currentY =
static_cast<float>(win->h - mouseY);
6007 float currentX =
static_cast<float>(mouseX);
6008 if (mouseState & SDL_BUTTON(SDL_BUTTON_LEFT)) {
6020 if (mouseState & SDL_BUTTON(SDL_BUTTON_RIGHT)) {
6029 glUniform4f(iMouseLoc, currentX, currentY, leftClickFlag, rightClickFlag);
6035 GLint iResolution = names[
index()].iResolution;
6036 glUniform2f(iResolution, win->w, win->h);
6037 if (names[
index()].time_speed_loc != -1) {
6047 GLuint amp_i = names[
index()].amp;
6048 float amplitude = 1.0f;
6049 float dt_scale =
audio_delta ?
static_cast<float>(delta_time) : 1.0f;
6050 float new_amp = audio_metrics.amplitude * audio_sensitivity *
time_speed * dt_scale;
6051 if (std::isnan(new_amp) || std::isinf(new_amp) || new_amp > 1e6f) {
6054 amplitude = new_amp;
6057 GLuint amp_u = names[
index()].amp_untouched;
6058 glUniform1f(amp_u, audio_metrics.amplitude);
6059 GLint iSampleRateLoc = names[
index()].iSampleRate;
6060 if (iSampleRateLoc != -1) {
6062 glUniform1f(iSampleRateLoc, sample_rate);
6064 if (names[
index()].iamp != -1) {
6065 glUniform1f(names[
index()].iamp, audio_metrics.frequency);
6069 auto &n = names[
index()];
6070 if (n.amp_peak != -1) {
6071 glUniform1f(n.amp_peak, std::sqrt(audio_metrics.peak) * sense);
6073 if (n.amp_rms != -1) {
6074 glUniform1f(n.amp_rms, std::sqrt(audio_metrics.rms) * sense);
6076 if (n.amp_smooth != -1) {
6077 glUniform1f(n.amp_smooth, std::sqrt(audio_metrics.smooth) * sense);
6079 if (n.amp_low != -1) {
6080 glUniform1f(n.amp_low, std::sqrt(audio_metrics.low) * sense);
6082 if (n.amp_mid != -1) {
6083 glUniform1f(n.amp_mid, std::sqrt(audio_metrics.mid) * sense);
6085 if (n.amp_high != -1) {
6086 glUniform1f(n.amp_high, std::sqrt(audio_metrics.high) * sense);
6089 if (names[
index()].spectrum_loc != -1) {
6092 auto &spectrum_names = names[
index()];
6093 if (spectrum_names.spectrum_zero_loc != -1)
6095 if (spectrum_names.spectrum_history_loc != -1)
6097 if (spectrum_names.spectrum_history_head_loc != -1)
6099 if (spectrum_names.spectrum_history_size_loc != -1)
6103 auto &n = names[
index()];
6104 if (n.history_loc != -1)
6105 glUniform1i(n.history_loc, 1);
6106 if (n.history_head_loc != -1)
6110 for (
int i = 0; i < 4; ++i) {
6111 if (names[
index()].slider_loc[i] != -1)
6148 random_var = glm::vec4(rand() % 255, rand() % 255, rand() % 255, rand() % 255);
6162 for (std::size_t i = 0; i < count; ++i) {
6185 glUniform1f(n.
index_value,
static_cast<float>(idx));
6206 constexpr float TIME_F_WRAP = 65536.0f * 6.2831853f;
6208 if (
time_f >= TIME_F_WRAP)
6212 mx::system_out <<
"acmx2: Time stepped forward: " <<
time_f <<
"\n";
6226 constexpr float TWO_PI = 6.2831853f;
6230 time_f = std::fmod(std::fmod(
time_f - value, TWO_PI) + TWO_PI, TWO_PI);
6236 mx::system_out <<
"acmx2: Time stepped back (wrapped): " <<
time_f <<
"\n";
6252 std::string enabled = ((t ==
true) ?
"on" :
"off");
6253 mx::system_out <<
"acmx2: active time: " << enabled <<
"\n";
6268 std::string enabled = ((t ==
true) ?
"on" :
"off");
6269 mx::system_out <<
"acmx2: audio time: " << enabled <<
"\n";
6281 mx::system_out <<
"acmx2: audio delta time: " << (
audio_delta ?
"on" :
"off") <<
"\n";
6310 AVFormatContext *format_context =
nullptr;
6311 if (avformat_open_input(&format_context, filename.c_str(),
nullptr,
nullptr) < 0) {
6312 return std::nullopt;
6315 std::optional<cv::Size> dimensions;
6316 if (avformat_find_stream_info(format_context,
nullptr) >= 0) {
6317 const int stream_index = av_find_best_stream(format_context, AVMEDIA_TYPE_VIDEO, -1, -1,
nullptr, 0);
6318 if (stream_index >= 0) {
6319 const AVCodecParameters *parameters = format_context->streams[stream_index]->codecpar;
6320 if (parameters->width > 0 && parameters->height > 0) {
6321 dimensions = cv::Size(parameters->width, parameters->height);
6326 avformat_close_input(&format_context);
6356 std::optional<cv::Size>
sizev = std::nullopt;
6357 std::optional<cv::Size>
csize = std::nullopt;
6360 std::tuple<int, std::string, int>
slib;
6587 auto now = std::chrono::steady_clock::now();
6590 if (delta_time < 0.0f)
6608 std::map<std::pair<unsigned char, unsigned char>,
unsigned char>
knobState;
6734 return "TimeToggle";
6792 void initMidi(
const std::string &mapFile,
int deviceIndex) {
6795 midiIn->ignoreTypes(
false,
false,
false);
6796 unsigned int ports =
midiIn->getPortCount();
6798 mx::system_out <<
"acmx2: No MIDI input devices found\n";
6803 unsigned int port = (deviceIndex >= 0 && deviceIndex < static_cast<int>(ports)) ?
static_cast<unsigned int>(deviceIndex) : 0;
6804 mx::system_out <<
"acmx2: Opening MIDI port " << port <<
": " <<
midiIn->getPortName(port) <<
"\n";
6808 std::ifstream file(mapFile);
6809 if (!file.is_open()) {
6810 mx::system_err <<
"acmx2: Could not open MIDI map file: " << mapFile <<
"\n";
6814 while (std::getline(file, line)) {
6815 std::istringstream iss(line);
6816 std::string keyPair;
6817 if (!(iss >> keyPair))
6819 auto colonPos = keyPair.find(
':');
6820 if (colonPos == std::string::npos)
6822 int k1 = std::stoi(keyPair.substr(0, colonPos));
6823 int k2 = std::stoi(keyPair.substr(colonPos + 1));
6825 if (!(iss >> brace) || brace !=
'{')
6828 if (!(iss >> b0 >> b1 >> b2))
6830 midiCodes.push_back({k1, k2,
static_cast<unsigned char>(b0),
static_cast<unsigned char>(b1),
static_cast<unsigned char>(b2)});
6832 mx::system_out <<
"acmx2: Loaded " <<
midiCodes.size() <<
" MIDI mapping(s)\n";
6834 }
catch (RtMidiError &e) {
6835 mx::system_err <<
"acmx2: MIDI error: " << e.getMessage() <<
"\n";
6866 return SDLK_PAGEDOWN;
6890 return SDLK_LEFTBRACKET;
6892 return SDLK_RIGHTBRACKET;
6964 return SDLK_UNKNOWN;
6966 return SDLK_UNKNOWN;
6977 ev.type = SDL_KEYUP;
6978 ev.key.keysym.sym = key;
6979 ev.key.state = SDL_RELEASED;
6999 std::vector<unsigned char> msg;
7001 midiIn->getMessage(&msg);
7005 if (msg[0] == mc.b0 && msg[1] == mc.b1) {
7009 }
else if (msg[2] == mc.b2) {
7012 if (k != SDLK_UNKNOWN) {
7016 }
else if (mc.key1 == 510) {
7024 }
else if (mc.key1 == 511) {
7044 auto key = std::make_pair(mc.b0, mc.b1);
7048 unsigned char val = it->second;
7050 if (mc.key1 >= 600 && mc.key1 <= 606 && (mc.key1 % 2 == 0)) {
7051 int idx = (mc.key1 - 600) / 2;
7057 int dist = (val > 64) ? (val - 64) : (64 - val);
7060 int skipFrames = std::max(1, 17 - (dist * 16 / 63));
7062 if (++counter >= skipFrames) {
7067 bool useDelta = (mc.key1 == 506 || mc.key1 == 508 || mc.key1 == 512);
7071 unsigned char prev = (prevIt !=
knobPrevValue.end()) ? prevIt->second : 64;
7072 activeKey = (val >= prev) ? mc.key1 : mc.key2;
7075 activeKey = (val > 64) ? mc.key1 : mc.key2;
7078 if (activeKey == 504) {
7080 }
else if (activeKey == 505) {
7082 }
else if (activeKey == 506) {
7085 mx::system_out <<
"acmx2: Model RotX: " <<
modelRotX <<
"\n";
7087 }
else if (activeKey == 507) {
7090 mx::system_out <<
"acmx2: Model RotX: " <<
modelRotX <<
"\n";
7092 }
else if (activeKey == 508) {
7095 mx::system_out <<
"acmx2: Model RotY: " <<
modelRotY <<
"\n";
7097 }
else if (activeKey == 509) {
7100 mx::system_out <<
"acmx2: Model RotY: " <<
modelRotY <<
"\n";
7102 }
else if (activeKey == 512) {
7105 mx::system_out <<
"acmx2: Model RotZ: " <<
modelRotZ <<
"\n";
7107 }
else if (activeKey == 513) {
7110 mx::system_out <<
"acmx2: Model RotZ: " <<
modelRotZ <<
"\n";
7112 }
else if (activeKey == 514) {
7114 mx::system_out <<
"acmx2: Model scale increased to " <<
modelRenderScale <<
"\n";
7116 }
else if (activeKey == 515) {
7120 mx::system_out <<
"acmx2: Model scale decreased to " <<
modelRenderScale <<
"\n";
7124 if (k != SDLK_UNKNOWN)
7157 win->text.setColor({0, 255, 0, 255});
7158 win->text.printText_Blended(font, 10, y,
"MIDI Active");
7161 size_t maxLabelLen = 0;
7166 if (len > maxLabelLen)
7170 win->text.setColor({0, 255, 0, 255});
7174 auto it =
knobState.find({mc.b0, mc.b1});
7175 unsigned char val = (it !=
knobState.end()) ? it->second : 64;
7178 int pos = (val * barLen) / 127;
7179 std::string bar(barLen,
'-');
7180 bar[barLen / 2] =
'|';
7185 while (label.size() < maxLabelLen)
7187 std::ostringstream oss;
7188 oss << label <<
" [" << bar <<
"] " << std::setw(3) <<
static_cast<int>(val) <<
" " << dir;
7189 win->text.printText_Blended(font, 10, y, oss.str());
7193 auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() -
lastMidiButtonTime).count();
7195 int alpha = (elapsed < 1500) ? 255 : 255 -
static_cast<int>((elapsed - 1500) * 255 / 500);
7196 win->text.setColor({0, 255, 0,
static_cast<unsigned char>(std::max(0, alpha))});
7197 win->text.printText_Blended(font, 10, y,
"Button: " +
lastMidiButton);
7206#ifdef ACMX2_WITH_CUDA
7207 cudaGraphicsResource *recordCudaPboResources[2] = {
nullptr,
nullptr};
7246 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, w, h, 0, GL_RGBA, GL_HALF_FLOAT,
nullptr);
7247 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
7248 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
7249 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
7250 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
7251 }
else if (resize_needed) {
7253 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, w, h, 0, GL_RGBA, GL_HALF_FLOAT,
nullptr);
7254 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
7255 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
7256 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
7257 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
7263 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
7264 mx::system_err <<
"acmx2: HDR linear-video FBO incomplete\n";
7266 glBindFramebuffer(GL_FRAMEBUFFER, 0);
7274 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16, w, h, 0, GL_RGBA, GL_UNSIGNED_SHORT,
nullptr);
7275 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
7276 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
7277 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
7278 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
7279 }
else if (resize_needed) {
7281 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16, w, h, 0, GL_RGBA, GL_UNSIGNED_SHORT,
nullptr);
7282 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
7283 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
7284 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
7285 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
7290 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
hdr_encoded_texture, 0);
7291 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
7292 mx::system_err <<
"acmx2: HDR encoded FBO incomplete\n";
7294 glBindFramebuffer(GL_FRAMEBUFFER, 0);
7298 mx::system_err <<
"acmx2: failed to compile HDR decode shader\n";
7303 mx::system_err <<
"acmx2: failed to compile HDR encode shader\n";
7308 mx::system_err <<
"acmx2: failed to compile display flip shader\n";
7327 const int transfer_mode = (
input_hdr_trc == AVCOL_TRC_ARIB_STD_B67) ? 2 : 1;
7330 glGetIntegerv(GL_FRAMEBUFFER_BINDING, &prev_fbo);
7331 GLint prev_viewport[4] = {0};
7332 glGetIntegerv(GL_VIEWPORT, prev_viewport);
7335 glViewport(0, 0, w, h);
7336 glDisable(GL_DEPTH_TEST);
7337 glDisable(GL_BLEND);
7338 glClear(GL_COLOR_BUFFER_BIT);
7344 glUniform1i(glGetUniformLocation(
hdr_decode_shader.id(),
"transfer"), transfer_mode);
7349 glBindFramebuffer(GL_FRAMEBUFFER, prev_fbo);
7350 glViewport(prev_viewport[0], prev_viewport[1], prev_viewport[2], prev_viewport[3]);
7361 const int transfer_mode = (
input_hdr_trc == AVCOL_TRC_ARIB_STD_B67) ? 2 : 1;
7364 glGetIntegerv(GL_FRAMEBUFFER_BINDING, &prev_fbo);
7365 GLint prev_viewport[4] = {0};
7366 glGetIntegerv(GL_VIEWPORT, prev_viewport);
7369 glViewport(0, 0, w, h);
7370 glDisable(GL_DEPTH_TEST);
7371 glDisable(GL_BLEND);
7372 glClear(GL_COLOR_BUFFER_BIT);
7378 glUniform1i(glGetUniformLocation(
hdr_encode_shader.id(),
"transfer"), transfer_mode);
7381 sprite.draw(src_tex, 0, 0, w, h);
7383 glBindFramebuffer(GL_FRAMEBUFFER, prev_fbo);
7384 glViewport(prev_viewport[0], prev_viewport[1], prev_viewport[2], prev_viewport[3]);
7402 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16, rgba16.cols, rgba16.rows, 0, GL_RGBA, GL_UNSIGNED_SHORT,
nullptr);
7403 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
7404 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
7405 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
7406 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
7410 glPixelStorei(GL_UNPACK_ALIGNMENT, 2);
7411 glPixelStorei(GL_UNPACK_ROW_LENGTH,
static_cast<GLint
>(rgba16.step / (4 *
sizeof(uint16_t))));
7412 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, rgba16.cols, rgba16.rows, GL_RGBA, GL_UNSIGNED_SHORT, rgba16.ptr());
7413 glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
7414 glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
7415 glBindTexture(GL_TEXTURE_2D, 0);
7423 for (
int pass = 0; pass < 2; ++pass) {
7425 glDeleteFramebuffers(1, &
passFBO[pass]);
7431 for (
int pass = 0; pass < 2; ++pass) {
7432 glGenFramebuffers(1, &
passFBO[pass]);
7435 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, win->w, win->h, 0, GL_RGBA, GL_HALF_FLOAT,
nullptr);
7436 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
7437 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
7438 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
7439 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
7440 glBindFramebuffer(GL_FRAMEBUFFER,
passFBO[pass]);
7441 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
passTexture[pass], 0);
7442 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
7443 throw mx::Exception(
"acmx2: shader pass framebuffer is not complete");
7456 glActiveTexture(GL_TEXTURE1);
7459 for (
int cache_index = 0; cache_index <
library.
cacheSize(); ++cache_index) {
7460 glActiveTexture(GL_TEXTURE1 + cache_index);
7464 glActiveTexture(GL_TEXTURE0);
7467 bool runComputePass(gl::GLWindow *win,
size_t shader_index, GLuint input_texture, GLuint output_texture) {
7498 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16, w, h, 0, GL_RGBA, GL_UNSIGNED_SHORT,
nullptr);
7499 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
7500 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
7501 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
7502 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
7503 glBindTexture(GL_TEXTURE_2D, 0);
7511 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, w, h, 0, GL_RGBA, GL_HALF_FLOAT,
nullptr);
7512 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
7513 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
7514 glBindTexture(GL_TEXTURE_2D, 0);
7518 auto respecFloat = [&](GLuint tex) {
7521 glBindTexture(GL_TEXTURE_2D, tex);
7522 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, w, h, 0, GL_RGBA, GL_HALF_FLOAT,
nullptr);
7523 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
7524 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
7525 glBindTexture(GL_TEXTURE_2D, 0);
7532 for (
int p = 0; p < 2; ++p) {
7550 void hdrReadback(GLuint src_linear_tex,
int w,
int h, std::vector<unsigned char> &out) {
7552 const size_t bytes =
static_cast<size_t>(w) * h * 8;
7555 glPixelStorei(GL_PACK_ALIGNMENT, 2);
7556 glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_SHORT, out.data());
7557 glPixelStorei(GL_PACK_ALIGNMENT, 4);
7558 glBindTexture(GL_TEXTURE_2D, 0);
7572 ACView(
const MXArguments &args) :
crf{args.
crf},
encode_opts{args.
encode_opts},
prefix_path{args.
prefix_path},
filename{args.
filename},
ofilename{args.
ofilename},
graphic{args.graphic_file},
camera_index{args.camera_device},
flib{args.slib},
sizev{args.
sizev},
sizec{args.csize},
fps{args.fps_value},
repeat{args.
repeat},
full{args.
full},
frame_cache{static_cast<std::size_t>(args.cache_size > 0 ? args.cache_size : 8), args.cache_array},
texture_cache{args.cache},
texture_cache_array{args.cache_array},
cache_delay{args.
cache_delay},
copy_audio{args.
copy_audio},
gpu_cuda_device{args.cuda_device},
silent_mode{args.silent},
no_drop_mode{args.no_drop},
use_shader_cache_flag{args.use_shader_cache},
flip_output{args.
flip_output},
frame_rotation{args.
frame_rotation},
png_video_mode{args.png_output && !args.
filename.empty()},
generate_mode{args.
generate_interval > 0},
generate_interval{args.
generate_interval},
display_filter{args.
display_filter} {
7574 enableWatermark = true;
7575 watermark_text = args.watermark_text;
7586 if (file_audio_open(args.audio_file)) {
7587 audio_is_enabled = true;
7588 file_audio_mode = true;
7589 audio_file_path = args.audio_file;
7590 audio_trunc_mode = args.audio_trunc;
7591 audio_repeat_mode = args.audio_repeat;
7592 file_audio_set_repeat(audio_repeat_mode);
7593 audio_engine.analyzer().reset();
7594 audio_engine.analyzer().set_sample_rate(44100);
7595 audio_engine.analyzer().set_sensitivity(args.audio_sensitivty);
7596 if (args.audio_pass_through) {
7597 if (!file_audio_enable_output(audio_output_device)) {
7598 mx::system_err <<
"acmx2: File audio playback could not be opened; "
7599 "continuing with visual reactivity only\n";
7601 mx::system_out <<
"acmx2: File audio output is the master clock; "
7602 "late video frames will be dropped\n";
7605 resetAudioWarmupEnvelope();
7607 audio_buffer_count = spectrumHistory.init(std::max(args.audio_buffers, 0));
7608 if (audio_buffer_count > 0) {
7609 library.setAudioBufferCount(audio_buffer_count);
7611 mx::system_out <<
"acmx2: File audio enabled from: " << args.audio_file <<
"\n";
7612 mx::system_out <<
"acmx2: FFT spectrum texture initialised (" << acmx2::audio::AudioAnalyzer::spectrum_bin_count() <<
" bins on GL_TEXTURE" << SpectrumTexture::SPECTRUM_TEXTURE_UNIT <<
")\n";
7613 if (audio_buffer_count > 0) {
7614 mx::system_out <<
"acmx2: Audio spectrum history array enabled (" << audio_buffer_count <<
" layers in one GL_TEXTURE_1D_ARRAY on GL_TEXTURE" << SpectrumHistory::TEXTURE_UNIT <<
")\n";
7617 mx::system_err <<
"acmx2: Error could not open audio file: " << args.audio_file <<
"\n";
7619 }
else if (args.audio_enabled) {
7620 const acmx2::audio::AudioStreamConfig audio_config{
7621 args.audio_channels,
7622 args.audio_sensitivty,
7624 audio_output_device,
7625 args.audio_pass_through,
7628 mx::system_err <<
"acmx2: Error could not initialize audio\n";
7630 audio_is_enabled = true;
7631 audio_engine.recorder().set_gain(args.record_gain);
7632 resetAudioWarmupEnvelope();
7634 audio_buffer_count = spectrumHistory.init(std::max(args.audio_buffers, 0));
7635 if (audio_buffer_count > 0) {
7636 library.setAudioBufferCount(audio_buffer_count);
7646 library.is3D(args.is3d);
7647 library.setTimeSpeed(args.time_speed);
7648 library.setVideoFPS(args.fps_value);
7649 library.setNormalizedTime(args.normalized_time);
7650 is3d_enabled = args.is3d;
7651 m_file = args.model_file;
7653#ifdef ACMX2_WITH_DNN
7654 if (!args.human_model.empty()) {
7656 human_model_path = args.human_model;
7657 human_background_only = args.human_background_only;
7658 human_black_point = args.human_black;
7659 human_white_point = args.human_white;
7660 human_seg_model = std::make_unique<ac_dnn::PPHS>(human_model_path);
7661 mx::system_out <<
"acmx2: Human segmentation (PPHS) enabled with model: " << human_model_path <<
" [automatic CPU/CUDA selection]" << (human_background_only ?
" [background-only shader mode]" :
"") <<
"\n";
7662 } catch (
const std::exception &e) {
7663 mx::system_err <<
"acmx2: Failed to load human segmentation model '" << args.human_model <<
"': " << e.what() <<
"\n";
7664 human_seg_model.reset();
7666 }
else if (args.human_background_only) {
7667 mx::system_err <<
"acmx2: --background was specified without --human; ignoring.\n";
7669 if (!args.edge_model.empty()) {
7671 edge_det_model = std::make_unique<ac_dnn::Dexined>(args.edge_model);
7672 mx::system_out <<
"acmx2: Edge detection (Dexined) enabled with model: " << args.edge_model <<
" [automatic CPU/CUDA selection]\n";
7673 } catch (
const std::exception &e) {
7674 mx::system_err <<
"acmx2: Failed to load edge detection model '" << args.edge_model <<
"': " << e.what() <<
"\n";
7675 edge_det_model.reset();
7678 if (!args.onnx_model.empty()) {
7680 onnx_proc_model = std::make_unique<ac_dnn::OnnxWrapper>(args.onnx_model);
7681 mx::system_out <<
"acmx2: Generic ONNX model enabled from YAML: " << args.onnx_model <<
"\n";
7682 } catch (
const std::exception &e) {
7683 mx::system_err <<
"acmx2: Failed to load ONNX model '" << args.onnx_model <<
"': " << e.what() <<
"\n";
7684 onnx_proc_model.reset();
7688 if (!args.human_model.empty()) {
7689 mx::system_err <<
"acmx2: --human requested but this build has no OpenCV DNN support (configure with -DWITH_OPENCV_DNN=ON).\n";
7691 if (args.human_background_only) {
7692 mx::system_err <<
"acmx2: --background requested but this build has no OpenCV DNN support.\n";
7694 if (!args.edge_model.empty()) {
7695 mx::system_err <<
"acmx2: --edge requested but this build has no OpenCV DNN support (configure with -DWITH_OPENCV_DNN=ON).\n";
7697 if (!args.onnx_model.empty()) {
7698 mx::system_err <<
"acmx2: --onnx requested but this build has no OpenCV DNN support (configure with -DWITH_OPENCV_DNN=ON).\n";
7702 gpu_filter_enabled = args.gpu_filter_enabled;
7703#ifdef ACMX2_WITH_CUDA
7704 if (gpu_filter_enabled && !args.gpu_filter_indices.empty()) {
7705 for (int idx : args.gpu_filter_indices) {
7706 if (idx >= 0 && idx < ac_gpu::AC_FILTER_MAX) {
7707 gpu_filters.push_back({idx, ac_gpu::filters[idx].name});
7708 mx::system_out <<
"acmx2: GPU filter added: " << ac_gpu::filters[idx].name <<
" (index " << idx <<
")\n";
7711 gpu_current_filter_index = args.gpu_filter_indices[0];
7712 gpu_frame_buffer = std::make_unique<ac_gpu::DynamicFrameBuffer>(args.gpu_frame_buffer_size);
7713 CHECK_CUDA(cudaMalloc(&d_ptrList, args.gpu_frame_buffer_size *
sizeof(
unsigned char *)));
7714 mx::system_out <<
"acmx2: GPU filtering enabled with " << gpu_filters.size() <<
" filter(s)\n";
7717 if (gpu_filter_enabled) {
7718 mx::system_err <<
"acmx2: GPU filters requested but this build has no CUDA support; disabling.\n";
7719 gpu_filter_enabled =
false;
7722 counter_disabled = args.disable_counter;
7723 use_yuv = args.use_yuv;
7725 if (args.shader_pass_enabled && !args.shader_pass_list.empty()) {
7726 shader_pass_list = args.shader_pass_list;
7727 shader_pass_enabled = true;
7728 mx::system_out <<
"acmx2: Shader pass list enabled with " << shader_pass_list.size() <<
" shader(s)\n";
7729 for (int idx : shader_pass_list) {
7730 mx::system_out <<
" - Shader index: " << idx <<
"\n";
7734 playlist_file = args.playlist_file;
7735 autopilot_frames = args.autopilot_frames;
7736 autopilot_random_interval = args.autopilot_random_interval;
7737 autopilot_random_timeout = args.autopilot_random_timeout;
7738 resetAutopilotInterval();
7739 duration_limit = args.duration;
7740 max_size_limit_mb = args.max_size_mb;
7741 if (max_size_limit_mb > 0.0) {
7742 max_size_limit_bytes = max_size_limit_mb * 1024.0 * 1024.0;
7744 crossfadeDuration = args.cross_fade_duration;
7746 if (!args.midi_map_file.empty()) {
7747 initMidi(args.midi_map_file, args.midi_device);
7750#if defined(__linux__) || defined(__APPLE__) || defined(_WIN32)
7751 if (args.interface_shm) {
7752 initShaderSelectionSharedMemory();
7759#ifdef ACMX2_WITH_DNN
7760 std::unique_ptr<ac_dnn::PPHS> human_seg_model;
7761 std::string human_model_path;
7762 bool human_background_only =
false;
7763 float human_black_point = 0.35f;
7764 float human_white_point = 0.75f;
7765 std::unique_ptr<ac_dnn::Dexined> edge_det_model;
7766 std::unique_ptr<ac_dnn::OnnxWrapper> onnx_proc_model;
7767 GLuint human_overlay_tex = 0;
7768 int human_overlay_w = 0;
7769 int human_overlay_h = 0;
7770 bool human_overlay_ready =
false;
7777#ifdef ACMX2_WITH_CUDA
7778 cv::cuda::GpuMat gpuWorkingBuffer;
7779 cv::cuda::GpuMat gpu_rotation_input;
7780 cv::cuda::GpuMat gpu_rotation_output;
7781 cv::cuda::GpuMat onnxGpuOutput;
7823 const int lower = 4;
7825 std::uniform_int_distribution<int> dist(lower, upper);
7846 std::uniform_int_distribution<int> dist(0, n - 1);
7849 next = (next + 1) % n;
7858#if defined(__linux__) || defined(__APPLE__) || defined(_WIN32)
7859#if defined(__linux__) || defined(__APPLE__)
7860 int shaderSelectionShmFd = -1;
7862 HANDLE shaderSelectionMapping =
nullptr;
7865#if defined(__linux__) || defined(__APPLE__)
7866 sem_t *shaderSelectionSemaphore = SEM_FAILED;
7868 HANDLE shaderSelectionSemaphore =
nullptr;
7870 uint32_t shaderSelectionLastSequence = 0;
7871 uint32_t shaderReloadLastSequence = 0;
7872 uint32_t audioFileLastSequence = 0;
7875 std::vector<ShaderManifestData::CustomUniform> uniforms;
7877 uniforms.reserve(count);
7878 for (uint32_t i = 0; i < count; ++i) {
7888 uniform.
minimum = -std::numeric_limits<double>::max();
7889 uniform.
maximum = std::numeric_limits<double>::max();
7892 if (!std::isfinite(uniform.
value))
7893 uniform.
value = 0.0;
7894 uniforms.push_back(std::move(uniform));
7900#if defined(__linux__) || defined(__APPLE__)
7901 if (!shaderSelectionShm || shaderSelectionSemaphore == SEM_FAILED)
7904 if (!shaderSelectionShm || shaderSelectionSemaphore ==
nullptr)
7907 acmx2::ipc::ShaderSelectionLock lock(shaderSelectionSemaphore);
7913 snapshot = *shaderSelectionShm;
7917 void initShaderSelectionSharedMemory() {
7918 if (shaderSelectionShm)
7920#if defined(__linux__) || defined(__APPLE__)
7922 if (shaderSelectionSemaphore == SEM_FAILED) {
7927 if (shaderSelectionShmFd < 0) {
7928 ::sem_close(shaderSelectionSemaphore);
7929 shaderSelectionSemaphore = SEM_FAILED;
7934 if (mapped == MAP_FAILED) {
7935 ::close(shaderSelectionShmFd);
7936 shaderSelectionShmFd = -1;
7937 ::sem_close(shaderSelectionSemaphore);
7938 shaderSelectionSemaphore = SEM_FAILED;
7944 shaderSelectionSemaphore = ::OpenMutexW(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, acmx2::ipc::kShaderSelectionMutexNameWindows);
7945 if (shaderSelectionSemaphore ==
nullptr) {
7946 std::cerr <<
"acmx2: interface control unavailable: OpenMutexW failed "
7947 "with Windows error "
7948 << ::GetLastError() <<
'\n';
7952 shaderSelectionMapping = ::OpenFileMappingW(FILE_MAP_READ, FALSE, acmx2::ipc::kShaderSelectionMappingNameWindows);
7953 if (shaderSelectionMapping ==
nullptr) {
7954 std::cerr <<
"acmx2: interface control unavailable: "
7955 "OpenFileMappingW failed with Windows error "
7956 << ::GetLastError() <<
'\n';
7957 cleanupShaderSelectionSharedMemory();
7962 if (mapped ==
nullptr) {
7963 std::cerr <<
"acmx2: interface control unavailable: MapViewOfFile "
7964 "failed with Windows error "
7965 << ::GetLastError() <<
'\n';
7966 cleanupShaderSelectionSharedMemory();
7972 if (!copyShaderSelectionSnapshot(snapshot)) {
7973 cleanupShaderSelectionSharedMemory();
7977 shaderSelectionLastSequence = snapshot.
sequence;
7980 library.setCustomUniformValues(customUniformsFromSharedMemory(snapshot));
7983 void cleanupShaderSelectionSharedMemory() {
7984 if (shaderSelectionShm) {
7985#if defined(__linux__) || defined(__APPLE__)
7988 ::UnmapViewOfFile(shaderSelectionShm);
7990 shaderSelectionShm =
nullptr;
7992#if defined(__linux__) || defined(__APPLE__)
7993 if (shaderSelectionShmFd >= 0) {
7994 ::close(shaderSelectionShmFd);
7995 shaderSelectionShmFd = -1;
7997 if (shaderSelectionSemaphore != SEM_FAILED) {
7998 ::sem_close(shaderSelectionSemaphore);
7999 shaderSelectionSemaphore = SEM_FAILED;
8002 if (shaderSelectionMapping !=
nullptr) {
8003 ::CloseHandle(shaderSelectionMapping);
8004 shaderSelectionMapping =
nullptr;
8006 if (shaderSelectionSemaphore !=
nullptr) {
8007 ::CloseHandle(shaderSelectionSemaphore);
8008 shaderSelectionSemaphore =
nullptr;
8013 void syncShaderSelectionFromInterface(gl::GLWindow *win) {
8015 if (!copyShaderSelectionSnapshot(snapshot))
8018 if (selection->
sequence == shaderSelectionLastSequence)
8021 shaderSelectionLastSequence = selection->
sequence;
8022 const auto readBoundedText = [](
const char *buf, std::size_t cap) {
8023 std::size_t len = 0;
8024 while (len < cap && buf[len] !=
'\0') {
8027 return std::string(buf, len);
8029 const std::vector<std::string> interfaceShaderFiles = std::get<0>(flib) == 1 ?
sortedShaderLibraryEntries(std::get<1>(flib)) : std::vector<std::string>{};
8031 library.setCustomUniformValues(customUniformsFromSharedMemory(snapshot));
8037 if (!file_audio_mode) {
8038 mx::system_err <<
"acmx2: Ignoring live audio-file change because this process "
8039 "was not started in audio-file mode\n";
8040 }
else if (requestedAudioPath.empty()) {
8041 mx::system_err <<
"acmx2: Ignoring empty live audio-file request\n";
8043 audio_file_path = requestedAudioPath;
8050 resetAudioWarmupEnvelope();
8052 mx::system_err <<
"acmx2: Live audio-file output could not be opened; "
8053 "continuing with visual reactivity only\n";
8055 mx::system_out <<
"acmx2: Switched file audio to: " << requestedAudioPath <<
"\n";
8057 mx::system_err <<
"acmx2: Could not switch file audio to: " << requestedAudioPath <<
"\n";
8059 mx::system_out.flush();
8060 mx::system_err.flush();
8068 std::string reloadPath;
8069 size_t reloadIndex = 0;
8070 std::string reloadError;
8072 if (requestedReloadIndex < 0 || requestedPath.empty()) {
8073 reloadError =
"Invalid shader reload request from interface";
8074 }
else if (std::get<0>(flib) == 1) {
8076 if (
static_cast<size_t>(requestedReloadIndex) >= shaderFiles.size()) {
8077 reloadError =
"Shader reload index is outside the shader manifest: " + std::to_string(requestedReloadIndex);
8078 }
else if (!
resolveShaderPathInLibrary(std::get<1>(flib), shaderFiles[
static_cast<size_t>(requestedReloadIndex)], reloadPath)) {
8079 reloadError =
"Could not resolve shader reload path from the manifest";
8081 std::error_code requestedError;
8082 const auto canonicalRequested = std::filesystem::weakly_canonical(std::filesystem::path(requestedPath), requestedError);
8083 std::error_code expectedError;
8084 const auto canonicalExpected = std::filesystem::weakly_canonical(std::filesystem::path(reloadPath), expectedError);
8085 std::error_code equivalentError;
8086 const bool isExpected = !requestedError && !expectedError && std::filesystem::equivalent(canonicalRequested, canonicalExpected, equivalentError) && !equivalentError;
8087 const bool isPreview = !requestedError && !expectedError && std::filesystem::is_regular_file(canonicalRequested) &&
isComputeShaderFile(canonicalRequested.string()) ==
isComputeShaderFile(canonicalExpected.string()) &&
isEditorPreviewPath(std::filesystem::path(std::get<1>(flib)), canonicalRequested);
8088 if (!isExpected && !isPreview) {
8089 reloadError =
"Shader reload path does not match the requested library index";
8091 reloadIndex =
static_cast<size_t>(requestedReloadIndex);
8092 reloadPath = canonicalRequested.string();
8096 std::error_code requestedError;
8097 const auto canonicalRequested = std::filesystem::weakly_canonical(std::filesystem::path(requestedPath), requestedError);
8098 std::error_code loadedError;
8099 const auto canonicalLoaded = std::filesystem::weakly_canonical(std::filesystem::path(std::get<1>(flib)), loadedError);
8100 std::error_code equivalentError;
8101 const bool isExpected = !requestedError && !loadedError && std::filesystem::equivalent(canonicalRequested, canonicalLoaded, equivalentError) && !equivalentError;
8103 if (!isExpected && !isPreview) {
8104 reloadError =
"Saved shader is not the shader loaded by this process";
8106 reloadPath = canonicalRequested.string();
8110 if (reloadError.empty() && library.reloadProgram(win, reloadIndex, reloadPath, reloadError)) {
8112 cube.setShaderProgram(library.shader());
8113 sprite.setShader(library.shader());
8114 updateShaderNameCache();
8115 mx::system_out <<
"acmx2: Live reloaded shader " << reloadPath <<
"\n";
8116 mx::system_out.flush();
8119 mx::system_err <<
"acmx2: Live shader reload failed for " << requestedPath <<
":\n" << reloadError <<
"\n";
8120 mx::system_err.flush();
8125 std::vector<int> requestedPassList;
8128 for (uint32_t i = 0; i < clampedPassCount; ++i) {
8130 if (std::get<0>(flib) == 1) {
8132 if (!passName.empty())
8137 if (
static_cast<size_t>(passIndex) >= library.size())
8139 requestedPassList.push_back(passIndex);
8141 const bool requestedPassEnabled = selection->
shader_pass_enabled != 0 && !requestedPassList.empty();
8142 const bool multipassChanged = (shader_pass_enabled != requestedPassEnabled) || (shader_pass_list != requestedPassList);
8147 if (!random_multipass_mode && !playlist_enabled) {
8148 if (multipassChanged && requestedPassEnabled)
8149 beginCrossfade(win);
8150 shader_pass_list = requestedPassList;
8151 shader_pass_enabled = requestedPassEnabled;
8152 updateShaderNameCache();
8160 const bool requestedWatermarkEnabled = (selection->
watermark_enabled != 0) && !requestedWatermark.empty();
8161 enableWatermark = requestedWatermarkEnabled;
8162 watermark_text = requestedWatermark;
8163 watermark_r = std::clamp<int>(selection->
watermark_r, 0, 255);
8164 watermark_g = std::clamp<int>(selection->
watermark_g, 0, 255);
8165 watermark_b = std::clamp<int>(selection->
watermark_b, 0, 255);
8167#ifdef ACMX2_WITH_CUDA
8168 std::vector<int> requestedGpuFilters;
8170 requestedGpuFilters.reserve(clampedGpuCount);
8171 for (uint32_t i = 0; i < clampedGpuCount; ++i) {
8175 requestedGpuFilters.push_back(idx);
8178 const bool requestedGpuEnabled = (selection->
gpu_filter_enabled != 0) && !requestedGpuFilters.empty();
8179 const int requestedGpuBufferSize = std::clamp<int>(
static_cast<int>(selection->
gpu_buffer_size), 4, 32);
8181 std::vector<int> currentGpuFilters;
8182 currentGpuFilters.reserve(gpu_filters.size());
8183 for (
const auto &f : gpu_filters) {
8184 currentGpuFilters.push_back(f.index);
8187 const bool gpuBufferChanged = requestedGpuEnabled && (!gpu_frame_buffer || gpu_frame_buffer->arraySize != requestedGpuBufferSize);
8188 const bool gpuConfigChanged = (gpu_filter_enabled != requestedGpuEnabled) || (currentGpuFilters != requestedGpuFilters) || gpuBufferChanged;
8190 if (gpuConfigChanged) {
8191 if (requestedGpuEnabled) {
8192 gpu_filters.clear();
8193 for (
int idx : requestedGpuFilters) {
8196 gpu_filter_enabled = !gpu_filters.empty();
8197 if (gpu_filter_enabled) {
8198 gpu_current_filter_index = gpu_filters.front().index;
8199 if (!gpu_frame_buffer || gpu_frame_buffer->arraySize != requestedGpuBufferSize) {
8200 gpu_frame_buffer = std::make_unique<ac_gpu::DynamicFrameBuffer>(requestedGpuBufferSize);
8203 d_ptrList =
nullptr;
8205 CHECK_CUDA(cudaMalloc(&d_ptrList, requestedGpuBufferSize *
sizeof(
unsigned char *)));
8206 gpu_frame_index = 0;
8209 gpu_filtersChanged =
true;
8211 gpu_filter_enabled =
false;
8214 gpu_filter_enabled =
false;
8215 gpu_filters.clear();
8216 gpu_current_filter_index = 0;
8217 gpu_filtersChanged =
true;
8223 if (std::get<0>(flib) == 1) {
8225 if (!requestedName.empty())
8228 if (requestedIndex < 0)
8230 if (
static_cast<size_t>(requestedIndex) >= library.size())
8232 if (shaderLocked || random_multipass_mode)
8234 if (
static_cast<size_t>(requestedIndex) == library.index())
8237 beginCrossfade(win);
8238 library.setIndex(
static_cast<size_t>(requestedIndex));
8240 cube.setShaderProgram(library.shader());
8241 sprite.setShader(library.shader());
8242 updateShaderNameCache();
8247 static std::mt19937 rng(std::random_device{}());
8248 size_t shader_count =
library.size();
8249 if (shader_count == 0)
8251 std::uniform_int_distribution<int> count_dist(1, 5);
8252 std::uniform_int_distribution<int> shader_dist(0,
static_cast<int>(shader_count) - 1);
8253 int chain_len = count_dist(rng);
8256 for (
int i = 0; i < chain_len; ++i) {
8264 mx::system_out <<
"acmx2: Random multipass [";
8268 mx::system_out <<
", ";
8270 mx::system_out <<
"]\n";
8275 static std::mt19937 rng(std::random_device{}());
8276 size_t shader_count =
library.size();
8277 if (shader_count == 0)
8279 std::uniform_int_distribution<int> shader_dist(0,
static_cast<int>(shader_count) - 1);
8282 for (
int i = 0; i < 2; ++i) {
8290 mx::system_out <<
"acmx2: Short random multipass [";
8294 mx::system_out <<
", ";
8296 mx::system_out <<
"]\n";
8301 static std::mt19937 rng(std::random_device{}());
8302 size_t shader_count =
library.size();
8303 if (shader_count == 0)
8305 std::uniform_int_distribution<int> count_dist(1, 10);
8306 std::uniform_int_distribution<int> shader_dist(0,
static_cast<int>(shader_count) - 1);
8307 int chain_len = count_dist(rng);
8310 for (
int i = 0; i < chain_len; ++i) {
8318 mx::system_out <<
"acmx2: Long random multipass [";
8322 mx::system_out <<
", ";
8324 mx::system_out <<
"]\n";
8354 std::uniform_int_distribution<int> dist(0, n - 1);
8368 mx::system_out <<
"acmx2: Autopilot -> Node: " << node.name <<
" [" << node.shader_indices.size() <<
" shaders] (" << (
playlist_index + 1) <<
"/" << n <<
")\n";
8372 std::uniform_int_distribution<int> dist(0, n - 1);
8384 mx::system_out <<
"acmx2: Autopilot -> Playlist [" << (
playlist_index + 1) <<
"/" << n <<
"]\n";
8417 mx::system_out <<
"acmx2: Autopilot (sequential) -> Node: " << node.name <<
" [" << node.shader_indices.size() <<
" shaders] (" << (
playlist_index + 1) <<
"/" << n <<
")\n";
8429 mx::system_out <<
"acmx2: Autopilot (sequential) -> Playlist [" << (
playlist_index + 1) <<
"/" << n <<
"]\n";
8453 const GLint cf_internal =
input_is_hdr ? GL_RGBA16F : GL_RGBA;
8454 const GLenum cf_type =
input_is_hdr ? GL_HALF_FLOAT : GL_UNSIGNED_BYTE;
8458 glTexImage2D(GL_TEXTURE_2D, 0, cf_internal, width, height, 0, GL_RGBA, cf_type,
nullptr);
8459 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
8460 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
8462 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
crossfadeTexture, 0);
8463 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
8464 throw mx::Exception(
"acmx2: crossfade framebuffer is not complete");
8468 glTexImage2D(GL_TEXTURE_2D, 0, cf_internal, width, height, 0, GL_RGBA, cf_type,
nullptr);
8469 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
8470 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
8471 glBindFramebuffer(GL_FRAMEBUFFER, 0);
8486 glBindFramebuffer(GL_READ_FRAMEBUFFER,
captureFBO);
8488 glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, win->w, win->h);
8489 glBindTexture(GL_TEXTURE_2D, 0);
8490 glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
8512 auto elapsed = std::chrono::duration<float>(std::chrono::steady_clock::now() -
crossfadeStartTime).count();
8520 glViewport(0, 0, win->w, win->h);
8521 glClear(GL_COLOR_BUFFER_BIT);
8526 if (crossfadeShaderIndex < 0 || crossfadeShaderIndex >=
static_cast<int>(
crossfadeShaders.size()))
8529 activeCrossfade.useProgram();
8530 activeCrossfade.setUniform(
"mv_matrix", glm::mat4(1.0f));
8531 activeCrossfade.setUniform(
"proj_matrix", glm::mat4(1.0f));
8533 glActiveTexture(GL_TEXTURE0);
8534 glBindTexture(GL_TEXTURE_2D, currentTexture);
8535 activeCrossfade.setUniform(
"samp", 0);
8536 glActiveTexture(GL_TEXTURE1);
8538 activeCrossfade.setUniform(
"prev_samp", 1);
8539 sprite.setShader(&activeCrossfade);
8541 sprite.draw(currentTexture, 0, 0, win->w, win->h);
8542 glActiveTexture(GL_TEXTURE0);
8574#if defined(__linux__) || defined(__APPLE__) || defined(_WIN32)
8575 cleanupShaderSelectionSharedMemory();
8580#ifdef ACMX2_WITH_DNN
8581 if (human_overlay_tex != 0) {
8582 glDeleteTextures(1, &human_overlay_tex);
8583 human_overlay_tex = 0;
8587#ifdef ACMX2_WITH_CUDA
8607 glBindBuffer(GL_PIXEL_PACK_BUFFER,
pboIds[pending_pbo_index]);
8608 GLubyte *src =
static_cast<GLubyte *
>(glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY));
8611 std::vector<unsigned char> pixels(
win_w *
win_h * 4);
8612 std::memcpy(pixels.data(), src, pixels.size());
8613 glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
8615 std::vector<unsigned char> flipped_pixels(
win_w *
win_h * 4);
8616 for (
int y = 0; y <
win_h; ++y) {
8617 int src_row_start = y *
win_w * 4;
8618 int dest_row_start = (
win_h - 1 - y) *
win_w * 4;
8619 std::copy(pixels.begin() + src_row_start, pixels.begin() + src_row_start + (
win_w * 4), flipped_pixels.begin() + dest_row_start);
8623 fd.
pixels = std::move(flipped_pixels);
8631 std::lock_guard<std::mutex> lock(
queueMutex);
8636 glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
8638 std::this_thread::sleep_for(std::chrono::milliseconds(100));
8648 if (shouldFileAudioMux) {
8669 for (
int i = 0; i < 2; ++i) {
8670#ifdef ACMX2_WITH_CUDA
8671 if (recordCudaPboResources[i]) {
8672 CHECK_CUDA(cudaGraphicsUnregisterResource(recordCudaPboResources[i]));
8673 recordCudaPboResources[i] =
nullptr;
8676 static_cast<void>(i);
8681 glDeleteBuffers(2,
pboIds);
8701 for (
int p = 0; p < 2; ++p) {
8703 glDeleteFramebuffers(1, &
passFBO[p]);
8764 virtual void load(gl::GLWindow *win)
override {
8766 mx::system_out.flush();
8768#ifdef ACMX2_WITH_CUDA
8770 if (cuda_err != cudaSuccess) {
8771 throw mx::Exception(
"Failed to set CUDA device " + std::to_string(
gpu_cuda_device) +
": " + std::string(cudaGetErrorString(cuda_err)));
8773 mx::system_out <<
"acmx2: Using CUDA device: " <<
gpu_cuda_device <<
"\n";
8776 mx::system_out <<
"acmx2: CUDA support disabled (compiled without ACMX2_WITH_CUDA)\n";
8786 overlayFont.tryLoadFont(win->util.getFilePath(
"data/font.ttf"), 24);
8788 int w = 1280, h = 720;
8789 int frame_w = w, frame_h = h;
8794 throw mx::Exception(
"Graphics file not found: " +
graphic);
8801 mx::system_out <<
"acmx2: Graphics file loaded: " << w <<
"x" << h <<
" at FPS: " <<
fps <<
"\n";
8806 std::swap(frame_w, frame_h);
8808 if (
sizev.has_value()) {
8809 w =
sizev.value().width;
8810 h =
sizev.value().height;
8811 mx::system_out <<
"acmx2: Resolution stretched to: " << w <<
"x" << h <<
"\n";
8816 win->setWindowSize(w, h);
8820 SDL_GL_GetDrawableSize(win->getWindow(), &win->w, &win->h);
8821 if (win->w != w || win->h != h) {
8825 glViewport(0, 0, win->w, win->h);
8826 SDL_SetWindowPosition(win->getWindow(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
8832 writer.set_block_when_full(
true);
8835 mx::system_out <<
"acmx2: Pipeline mode => decode: graphic/image, encode: " << (
writer.is_hardware_encode() ?
"h264_nvenc (hardware)" :
"h264 (software)") <<
"\n";
8840 throw mx::Exception(
"Could not open output video file: " +
ofilename);
8845 mx::system_out <<
"acmx2: --no-drop is ignored in webcam mode; "
8846 "wall-clock timestamps and late-frame dropping remain active\n";
8850 const bool loopback_device = isV4l2LoopbackDevice(
camera_index);
8852 const bool loopback_device =
false;
8856#elif defined(__linux__)
8861 if (!
cap.isOpened()) {
8862 throw mx::Exception(
"Could not open camera index: " + std::to_string(
camera_index));
8864 cap.set(cv::CAP_PROP_BUFFERSIZE, 1);
8865 if (
sizec.has_value()) {
8866 cap.set(cv::CAP_PROP_FRAME_WIDTH,
sizec.value().width);
8867 cap.set(cv::CAP_PROP_FRAME_HEIGHT,
sizec.value().height);
8869 cap.set(cv::CAP_PROP_FRAME_WIDTH, win->w);
8870 cap.set(cv::CAP_PROP_FRAME_HEIGHT, win->h);
8873 cap.set(cv::CAP_PROP_FOURCC, cv::VideoWriter::fourcc(
'Y',
'U',
'Y',
'V'));
8875 cap.set(cv::CAP_PROP_FOURCC, cv::VideoWriter::fourcc(
'M',
'J',
'P',
'G'));
8876 const double requested_fps =
fps;
8877 const bool fps_configured =
cap.set(cv::CAP_PROP_FPS, requested_fps);
8878 w =
static_cast<int>(
cap.get(cv::CAP_PROP_FRAME_WIDTH));
8879 h =
static_cast<int>(
cap.get(cv::CAP_PROP_FRAME_HEIGHT));
8880 const double reported_fps =
cap.get(cv::CAP_PROP_FPS);
8885 if (loopback_device && requested_fps > 0.0) {
8886 fps = requested_fps;
8887 }
else if (reported_fps > 0.0) {
8890 fps = requested_fps;
8894 mx::system_out <<
"acmx2: Camera opened: " << w <<
"x" << h <<
" at FPS: " <<
fps;
8895 if (loopback_device && reported_fps > 0.0 && std::abs(reported_fps - requested_fps) > 0.05) {
8896 mx::system_out <<
" (loopback reports " << reported_fps <<
", requested " << requested_fps <<
")";
8897 }
else if (!fps_configured && requested_fps > 0.0) {
8898 mx::system_out <<
" (driver rejected requested " << requested_fps <<
")";
8900 mx::system_out <<
"\n";
8906 std::swap(frame_w, frame_h);
8909 if (
sizev.has_value()) {
8910 w =
sizev.value().width;
8911 h =
sizev.value().height;
8912 mx::system_out <<
"acmx2: Resolution stretched to: " << w <<
"x" << h <<
"\n";
8914 win->setWindowSize(w, h);
8915 SDL_GL_GetDrawableSize(win->getWindow(), &win->w, &win->h);
8916 if (win->w != w || win->h != h) {
8920 glViewport(0, 0, w, h);
8922 SDL_SetWindowPosition(win->getWindow(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
8928 cam_opts.realtime =
true;
8930 mx::system_out <<
"acmx2: Opened: " <<
ofilename <<
" for writing at: CRF: " << cam_opts.crf <<
" preset: " << cam_opts.preset <<
" codec: " << cam_opts.codec <<
" [realtime]"
8931 <<
" FPS: " <<
fps <<
"\n";
8932 mx::system_out <<
"acmx2: Pipeline mode => decode: camera, encode: " << (
writer.is_hardware_encode() ?
"(hardware)" :
"(software)") <<
"\n";
8934 mx::system_out <<
"acmx2: Webcam recording follows wall-clock timestamps; "
8935 "late frames will be dropped\n";
8938 throw mx::Exception(
"Could not open output video file: " +
ofilename);
8943 const char *decode_mode =
nullptr;
8945 decode_mode =
ffmpeg_reader.isHwDecodeEnabled() ?
"ffmpeg-cuda" :
"ffmpeg-software";
8955 mx::system_out <<
"acmx2: Video opened (FFmpeg decode): " << w <<
"x" << h <<
" at FPS: " <<
fps <<
" Total Frames: " <<
totalFrames <<
"\n";
8956 mx::system_out <<
"acmx2: FFmpeg CUDA decode: " << (
ffmpeg_reader.isHwDecodeEnabled() ?
"enabled" :
"unavailable/fallback") <<
"\n";
8968 const char *trc_label = (trc == AVCOL_TRC_SMPTE2084) ?
"PQ (SMPTE2084)" : (trc == AVCOL_TRC_ARIB_STD_B67) ?
"HLG (ARIB STD-B67)" : (trc == AVCOL_TRC_BT2020_10) ?
"BT.2020 10-bit" : (trc == AVCOL_TRC_BT2020_12) ?
"BT.2020 12-bit" :
"unknown";
8969 mx::system_out <<
"acmx2: ============================================================\n"
8970 <<
"acmx2: *** PROCESSING IN HDR MODE ***\n"
8971 <<
"acmx2: Source is HDR: " << trc_label <<
", " <<
ffmpeg_reader.getHdrBitDepth() <<
"-bit,"
8972 <<
" BT.2020 primaries\n"
8973 <<
"acmx2: ============================================================\n";
8974 mx::system_out <<
"acmx2: HDR input detected (primaries=" <<
ffmpeg_reader.getHdrPrimaries() <<
", transfer=" << trc <<
", bit_depth=" <<
ffmpeg_reader.getHdrBitDepth() <<
")\n";
8976 mx::system_out <<
"acmx2: *** CUDA GPU filters DISABLED for HDR input "
8977 "(colour math assumes 8-bit sRGB). Shader processing only. ***\n";
8981 mx::system_out <<
"acmx2: CUDA GPU filters not in use; shader processing only for HDR input.\n";
8995 mx::system_out <<
"acmx2: HDR output mode enabled: HEVC Main10 + BT.2020 + " << (
encode_opts.hdr.color_trc == AVCOL_TRC_ARIB_STD_B67 ?
"HLG" :
"PQ") <<
" (mastering side-data bytes=" <<
encode_opts.hdr.mastering_display.size() <<
", content-light bytes=" <<
encode_opts.hdr.content_light.size() <<
")\n";
9001 mx::system_out <<
"acmx2: HDR GL pipeline: camera_texture=GL_RGBA16, "
9002 "fboTexture=GL_RGBA16F, pass textures=GL_RGBA16F, "
9003 "decode/encode shaders ready.\n";
9006 decode_mode =
"opencv-ffmpeg";
9007#if CV_VERSION_MAJOR > 4 || (CV_VERSION_MAJOR == 4 && CV_VERSION_MINOR >= 5)
9008 std::vector<int> file_params = {cv::CAP_PROP_HW_ACCELERATION, cv::VIDEO_ACCELERATION_ANY};
9013 if (!
cap.isOpened()) {
9014 throw mx::Exception(
"Could not open video file: " +
filename);
9016 w =
static_cast<int>(
cap.get(cv::CAP_PROP_FRAME_WIDTH));
9017 h =
static_cast<int>(
cap.get(cv::CAP_PROP_FRAME_HEIGHT));
9018 fps =
cap.get(cv::CAP_PROP_FPS);
9025 mx::system_out <<
"acmx2: Video opened (OpenCV/FFmpeg fallback): " << w <<
"x" << h <<
" at FPS: " <<
fps <<
" Total Frames: " <<
totalFrames <<
"\n";
9027#if CV_VERSION_MAJOR > 4 || (CV_VERSION_MAJOR == 4 && CV_VERSION_MINOR >= 5)
9028 int hw_accel =
static_cast<int>(
cap.get(cv::CAP_PROP_HW_ACCELERATION));
9029 mx::system_out <<
"acmx2: HW Acceleration result: " << hw_accel << (hw_accel == cv::VIDEO_ACCELERATION_NONE ?
" (software/fallback)" : hw_accel == cv::VIDEO_ACCELERATION_ANY ?
" (auto preference)" : hw_accel == cv::VIDEO_ACCELERATION_VAAPI ?
" (VAAPI)" : hw_accel == cv::VIDEO_ACCELERATION_D3D11 ?
" (D3D11)" : hw_accel == cv::VIDEO_ACCELERATION_MFX ?
" (MFX)" : hw_accel == cv::VIDEO_ACCELERATION_DRM ?
" (DRM)" :
" (other)") <<
"\n";
9031 mx::system_out <<
"acmx2: OpenCV hardware-decoder reporting unavailable "
9033 << CV_VERSION <<
"\n";
9038 if (SDL_GL_SetSwapInterval(0) == 0) {
9039 mx::system_out <<
"acmx2: VSync disabled for high-frame-rate video input (" <<
fps <<
" FPS)\n";
9041 mx::system_err <<
"acmx2: Could not disable VSync for " <<
fps <<
" FPS video input: " << SDL_GetError() <<
"\n";
9047 std::swap(frame_w, frame_h);
9053 if (
sizev.has_value()) {
9054 w =
sizev.value().width;
9055 h =
sizev.value().height;
9056 mx::system_out <<
"acmx2: Resolution stretched to: " << w <<
"x" << h <<
"\n";
9061 win->setWindowSize(w, h);
9062 SDL_GL_GetDrawableSize(win->getWindow(), &win->w, &win->h);
9063 if (win->w != w || win->h != h) {
9067 glViewport(0, 0, w, h);
9068 SDL_SetWindowPosition(win->getWindow(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
9072 std::filesystem::path out_path(
ofilename);
9073 std::filesystem::path out_parent = out_path.parent_path();
9074 if (out_parent.empty()) {
9077 std::string out_name = out_path.filename().string();
9078 if (out_name.empty()) {
9079 out_name =
"output";
9081 std::filesystem::path frame_dir = out_parent / (
"video_file-" + out_name +
"-png");
9082 std::error_code mk_err;
9083 if (!std::filesystem::exists(frame_dir, mk_err) && !std::filesystem::create_directories(frame_dir, mk_err)) {
9084 throw mx::Exception(
"Could not create PNG output directory: " + frame_dir.string());
9087 mx::system_out <<
"acmx2: --png enabled: writing video frames to " <<
png_video_dir <<
"\n";
9091 if (block_encoder_queue) {
9095 writer.set_block_when_full(
true);
9099 mx::system_out <<
"acmx2: --no-drop active (video mode): frame processing paced to encoder throughput\n";
9102 mx::system_out <<
"acmx2: *** HDR OUTPUT ENABLED: writing HEVC Main10 + BT.2020 " << (
encode_opts.hdr.color_trc == AVCOL_TRC_ARIB_STD_B67 ?
"HLG" :
"PQ") <<
" ***\n";
9104 mx::system_out <<
"acmx2: Pipeline mode => decode: " << decode_mode <<
", encode: " << (
writer.is_hardware_encode() ?
"(hardware)" :
"(software)") <<
"\n";
9108 throw mx::Exception(
"Could not open output video file: " +
ofilename);
9113 throw mx::Exception(
"Requires input from a file, or camera.");
9117 std::filesystem::path gen_dir;
9119 std::filesystem::path out_path(
ofilename);
9120 std::filesystem::path out_parent = out_path.parent_path();
9121 if (out_parent.empty())
9123 std::string out_name = out_path.filename().string();
9124 if (out_name.empty())
9125 out_name =
"output";
9126 gen_dir = out_parent / (
"video_file-" + out_name +
"-generate");
9128 std::filesystem::path in_path(
filename);
9129 std::filesystem::path in_parent = in_path.parent_path();
9130 if (in_parent.empty())
9132 std::string in_name = in_path.filename().string();
9133 if (in_name.empty())
9135 gen_dir = in_parent / (
"video_file-" + in_name +
"-generate");
9137 gen_dir = std::filesystem::path(
"camera-generate");
9139 std::error_code mk_err;
9140 if (!std::filesystem::exists(gen_dir, mk_err) && !std::filesystem::create_directories(gen_dir, mk_err)) {
9141 throw mx::Exception(
"Could not create generate output directory: " + gen_dir.string());
9151 win->text.init(win->w, win->h);
9152 win->text.setColor({255, 255, 255, 255});
9154 waterFontSize = std::max(12,
static_cast<int>(win->h / 40.0f));
9156 mx::system_out <<
"acmx2: Watermark font loaded at size: " <<
waterFontSize <<
" for " << win->w <<
"x" << win->h <<
"\n";
9160 static constexpr unsigned char kWhite[4] = {255, 255, 255, 255};
9163 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, kWhite);
9164 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
9165 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
9166 glBindTexture(GL_TEXTURE_2D, 0);
9180 if (std::get<0>(
flib) == 1) {
9188#if defined(__linux__) || defined(__APPLE__) || defined(_WIN32)
9189 if (shaderSelectionShm) {
9191 if (copyShaderSelectionSnapshot(snapshot))
9192 library.setCustomUniformValues(customUniformsFromSharedMemory(snapshot));
9198 if (!pfile.is_open()) {
9199 mx::system_err <<
"acmx2: Error could not open playlist: " <<
playlist_file <<
"\n";
9204 while (std::getline(pfile, line)) {
9207 if (line.front() ==
'[' && line.back() ==
']') {
9208 playlist_tree.push_back({line.substr(1, line.size() - 2), {}});
9214 const std::string name = std::filesystem::path(line).stem().string();
9215 idx =
library.findShaderByName(name);
9223 mx::system_err <<
"acmx2: Playlist shader not found: " << line <<
"\n";
9231 mx::system_out <<
" Node: " << node.name <<
" [" << node.shader_indices.size() <<
" shaders]\n";
9238 std::string m_file_path;
9239 if (std::filesystem::exists(
m_file)) {
9242 std::string m_file_base =
m_file;
9243 if (m_file_base.size() > 5 && m_file_base.substr(0, 5) ==
"data/")
9244 m_file_base = m_file_base.substr(5);
9245 m_file_path = win->util.getFilePath(
"data/" + m_file_base);
9249 throw mx::Exception(
"Could not open model: cube.mxmod.z");
9252 cube.saveOriginal();
9255 float minX = std::numeric_limits<float>::max();
9256 float minY = std::numeric_limits<float>::max();
9257 float minZ = std::numeric_limits<float>::max();
9258 float maxX = std::numeric_limits<float>::lowest();
9259 float maxY = std::numeric_limits<float>::lowest();
9260 float maxZ = std::numeric_limits<float>::lowest();
9261 for (
const auto &mesh :
cube.meshes) {
9262 for (
size_t i = 0; i + 2 < mesh.vert.size(); i += 3) {
9263 float x = mesh.vert[i];
9264 float y = mesh.vert[i + 1];
9265 float z = mesh.vert[i + 2];
9266 minX = std::min(minX, x);
9267 minY = std::min(minY, y);
9268 minZ = std::min(minZ, z);
9269 maxX = std::max(maxX, x);
9270 maxY = std::max(maxY, y);
9271 maxZ = std::max(maxZ, z);
9274 float dx = maxX - minX;
9275 float dy = maxY - minY;
9276 float dz = maxZ - minZ;
9277 modelSize = std::sqrt(dx * dx + dy * dy + dz * dz);
9280 glm::vec3 modelCenter = glm::vec3((minX + maxX) * 0.5f, (minY + maxY) * 0.5f, (minZ + maxZ) * 0.5f);
9282 float maxExtent = std::max(dx, std::max(dy, dz));
9283 const float targetSize = 2.5f;
9284 if (maxExtent > 1e-6f)
9286 mx::system_out <<
"acmx2: Model bounding diagonal: " <<
modelSize <<
"\n";
9290 if (!
fshader.loadProgram(win->util.getFilePath(
"data/vert.glsl"), win->util.getFilePath(
"data/framebuffer.glsl"))) {
9291 throw mx::Exception(
"Error loading shader");
9293 if (!
fshader3d.loadProgram(win->util.getFilePath(
"data/vertex.glsl"), win->util.getFilePath(
"data/framebuffer.glsl"))) {
9294 throw mx::Exception(
"Error loading shader");
9297 static const char *kCrossfadeFiles[] = {
9298 "data/xfade_01_linear.glsl",
"data/xfade_02_block.glsl",
"data/xfade_03_wipe.glsl",
"data/xfade_04_radial.glsl",
"data/xfade_05_pixelate.glsl",
"data/xfade_06_dissolve.glsl",
"data/xfade_07_swirl.glsl",
"data/xfade_08_glitch.glsl",
"data/xfade_09_diamond.glsl",
"data/xfade_10_burn.glsl",
"data/xfade_11_fade_black.glsl",
"data/xfade_12_fade_white.glsl",
"data/xfade_13_slide_left.glsl",
"data/xfade_14_slide_right.glsl",
"data/xfade_15_slide_up.glsl",
"data/xfade_16_slide_down.glsl",
"data/xfade_17_diagonal_wipe.glsl",
"data/xfade_18_iris_open.glsl",
9299 "data/xfade_19_iris_close.glsl",
"data/xfade_20_checker.glsl",
"data/xfade_21_blinds_h.glsl",
"data/xfade_22_blinds_v.glsl",
"data/xfade_23_zoom_in.glsl",
"data/xfade_24_zoom_out.glsl",
"data/xfade_25_rotate.glsl",
"data/xfade_26_ripple.glsl",
"data/xfade_27_wave.glsl",
"data/xfade_28_chroma.glsl",
"data/xfade_29_invert.glsl",
"data/xfade_30_flash.glsl",
"data/xfade_31_explode.glsl",
"data/xfade_32_mosaic.glsl",
"data/xfade_33_shutter.glsl",
"data/xfade_34_luma.glsl",
"data/xfade_35_noise.glsl",
9303 const std::string vertPath = win->util.getFilePath(
"data/vert.glsl");
9304 for (
const char *frag : kCrossfadeFiles) {
9305 gl::ShaderProgram prog;
9306 if (!prog.loadProgram(vertPath, win->util.getFilePath(frag))) {
9307 throw mx::Exception(std::string(
"Error loading crossfade shader: ") + frag);
9310 std::string nm = frag;
9311 auto slash = nm.find_last_of(
'/');
9312 if (slash != std::string::npos)
9313 nm = nm.substr(slash + 1);
9314 auto dot = nm.find_last_of(
'.');
9315 if (dot != std::string::npos)
9316 nm = nm.substr(0, dot);
9319 if (crossfadeShaderIndex < 0 || crossfadeShaderIndex >=
static_cast<int>(
crossfadeShaders.size()))
9322 GLenum error = glGetError();
9323 if (error != GL_NO_ERROR) {
9324 throw mx::Exception(
"OpenGL error occurred: GL Error: " + std::to_string(error));
9338 mx::system_out <<
"acmx2: Texture cache initalized.\n";
9341 sprite.initSize(win->w, win->h);
9352 const int up_w = (frame_w > 0) ? frame_w : win->w;
9353 const int up_h = (frame_h > 0) ? frame_h : win->h;
9365 const int upload_w = (frame_w > 0) ? frame_w : win->w;
9366 const int upload_h = (frame_h > 0) ? frame_h : win->h;
9367 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16, upload_w, upload_h, 0, GL_RGBA, GL_UNSIGNED_SHORT,
nullptr);
9368 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
9369 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
9370 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
9371 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
9372 glBindTexture(GL_TEXTURE_2D, 0);
9386 size_t pboSize = win->w * win->h * 4;
9387 for (
int i = 0; i < 2; i++) {
9388 glBindBuffer(GL_PIXEL_PACK_BUFFER,
pboIds[i]);
9389 glBufferData(GL_PIXEL_PACK_BUFFER, pboSize,
nullptr, GL_STREAM_READ);
9390#ifdef ACMX2_WITH_CUDA
9391 CHECK_CUDA(cudaGraphicsGLRegisterBuffer(&recordCudaPboResources[i],
pboIds[i], cudaGraphicsMapFlagsReadOnly));
9394 glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
9397 win->setWindowTitle(
"ACMX2 - Graphics Input");
9399 win->setWindowTitle(
"ACMX2 - Capture Input");
9401 win->setWindowTitle(
"ACMX2 - [" +
filename +
"]");
9404 win->setFullScreen(
true);
9407 if (
writer.is_open() ||
true ) {
9442#ifdef ACMX2_WITH_CUDA
9443 gpu_rotation_input.upload(frame);
9444 cv::Size destination_size = frame.size();
9445 double angle = 180.0;
9446 double shift_x =
static_cast<double>(frame.cols - 1);
9447 double shift_y =
static_cast<double>(frame.rows - 1);
9450 destination_size = cv::Size(frame.rows, frame.cols);
9452 shift_x =
static_cast<double>(frame.rows - 1);
9455 destination_size = cv::Size(frame.rows, frame.cols);
9458 shift_y =
static_cast<double>(frame.cols - 1);
9461 cv::cuda::rotate(gpu_rotation_input, gpu_rotation_output, destination_size, angle, shift_x, shift_y, cv::INTER_NEAREST);
9462 gpu_rotation_output.download(frame);
9464 int rotation_code = cv::ROTATE_180;
9466 rotation_code = cv::ROTATE_90_CLOCKWISE;
9468 rotation_code = cv::ROTATE_90_COUNTERCLOCKWISE;
9470 cv::rotate(frame, frame, rotation_code);
9498 virtual void draw(gl::GLWindow *win)
override {
9506 if (
fps > 0.0 && use_realtime_pacing) {
9507 auto now = std::chrono::steady_clock::now();
9508 auto frame_duration = std::chrono::microseconds(
static_cast<long long>(1000000.0 /
fps));
9512 auto elapsed = std::chrono::duration_cast<std::chrono::microseconds>(now -
lastFrameTime);
9513 if (elapsed < frame_duration) {
9514 std::this_thread::sleep_for(frame_duration - elapsed);
9523#if defined(__linux__) || defined(__APPLE__) || defined(_WIN32)
9524 syncShaderSelectionFromInterface(win);
9535 glViewport(0, 0, win->w, win->h);
9536 glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
9537 glClear(GL_COLOR_BUFFER_BIT);
9543 glUniform2f(glGetUniformLocation(
muxOverlayShader.id(),
"iResolution"),
static_cast<float>(win->w),
static_cast<float>(win->h));
9550 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
9551 win->text.setColor({255, 255, 255, 255});
9552 win->text.printText_Blended(
overlayFont, 10, 10,
"Muxing audio...");
9553 glDisable(GL_BLEND);
9559 double time_passed = 0.0;
9560 bool has_media_clock =
false;
9563 has_media_clock =
true;
9568 has_media_clock =
true;
9571 if (!has_media_clock &&
fps > 0.0) {
9581 mx::system_out <<
"acmx2: Duration limit reached (" <<
duration_limit <<
"s), stopping recording...\n";
9588 std::error_code size_error;
9589 const std::uintmax_t output_size = std::filesystem::file_size(
ofilename, size_error);
9591 const double current_size =
static_cast<double>(output_size);
9593 mx::system_out <<
"acmx2: Max size reached (" << std::fixed << std::setprecision(2) <<
max_size_limit_mb <<
" MB), stopping recording...\n";
9609 bool received_source_frame =
false;
9610 bool file_audio_clock_controls_video =
false;
9611 bool source_frame_uses_timeline_clock =
false;
9612 uint64_t source_timeline_frame = 0;
9613 bool stop_after_recording_drain =
false;
9618 received_source_frame = !
newFrame.empty();
9624 received_source_frame = !
newFrame.empty();
9630 source_frame_uses_timeline_clock =
true;
9632 const double elapsed_seconds = std::chrono::duration<double>(std::chrono::steady_clock::now() -
media_timeline_start_time).count();
9633 source_timeline_frame =
static_cast<uint64_t
>(std::floor(std::max(0.0, elapsed_seconds) *
fps));
9638 bool audio_clock_available =
false;
9639 double audio_clock_time = 0.0;
9642 file_audio_clock_controls_video = audio_clock_available;
9643 if (audio_clock_available) {
9650 target_frame =
static_cast<uint64_t
>(std::floor(std::max(0.0, audio_clock_time) *
fps));
9656 bool read_ok =
false;
9657 bool decoded_any_frame =
false;
9659 auto read_next_frame = [&](
bool discard) {
9664 mx::system_out <<
"acmx2: video loop...\n";
9672 mx::system_out <<
"acmx2: video loop...\n";
9673 cap.set(cv::CAP_PROP_POS_FRAMES, 0);
9683 for (uint64_t frame = 0; frame < frames_to_decode; ++frame) {
9684 const bool discard = frame + 1 < frames_to_decode;
9685 read_ok = read_next_frame(discard);
9689 decoded_any_frame =
true;
9693 if (decoded_any_frame) {
9700 mx::system_out <<
"acmx2: cannot read after looping.\n";
9704 if (can_drain_recording) {
9705 stop_after_recording_drain =
true;
9719 source_frame_uses_timeline_clock = audio_clock_available;
9735 if (received_source_frame && !
isFrozen) {
9742#ifdef ACMX2_WITH_CUDA
9743 bool onnxGpuFrameReady =
false;
9745#ifdef ACMX2_WITH_DNN
9753 human_overlay_ready =
false;
9756 cv::Mat mask = human_seg_model->infer(
newFrame);
9757 if (human_background_only) {
9759 if (!alpha8.empty() && alpha8.size() ==
newFrame.size()) {
9761 cv::cvtColor(
newFrame, rgba, cv::COLOR_BGR2RGBA);
9762 cv::insertChannel(alpha8, rgba, 3);
9765 if (human_overlay_tex == 0) {
9766 glGenTextures(1, &human_overlay_tex);
9767 glBindTexture(GL_TEXTURE_2D, human_overlay_tex);
9768 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
9769 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
9770 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
9771 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
9772 human_overlay_w = 0;
9773 human_overlay_h = 0;
9775 glBindTexture(GL_TEXTURE_2D, human_overlay_tex);
9776 if (human_overlay_w != rgba.cols || human_overlay_h != rgba.rows) {
9777 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, rgba.cols, rgba.rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, rgba.ptr());
9778 human_overlay_w = rgba.cols;
9779 human_overlay_h = rgba.rows;
9781 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, rgba.cols, rgba.rows, GL_RGBA, GL_UNSIGNED_BYTE, rgba.ptr());
9783 glBindTexture(GL_TEXTURE_2D, 0);
9784 human_overlay_ready =
true;
9790 cv::Mat inverseAlpha;
9791 cv::subtract(cv::Scalar::all(255), alpha8, inverseAlpha);
9793 cv::cvtColor(inverseAlpha, inverseBgr, cv::COLOR_GRAY2BGR);
9798 if (!isolated.empty()) {
9802 }
catch (
const cv::Exception &e) {
9803 mx::system_err <<
"acmx2: PPHS inference error: " << e.what() <<
"\n";
9811 edge_det_model->processFrame(
newFrame, edges);
9812 if (!edges.empty()) {
9813 if (edges.channels() == 1)
9814 cv::cvtColor(edges,
newFrame, cv::COLOR_GRAY2BGR);
9818 }
catch (
const cv::Exception &e) {
9819 mx::system_err <<
"acmx2: Dexined inference error: " << e.what() <<
"\n";
9828#ifdef ACMX2_WITH_CUDA
9830 if (!gpuFiltersNeedHostFrame)
9831 onnxGpuFrameReady = onnx_proc_model->procGpu(
newFrame, onnxGpuOutput);
9832 if (!onnxGpuFrameReady)
9836 onnx_proc_model->proc(
newFrame, onnx_out);
9837 if (!onnx_out.empty())
9840 }
catch (
const cv::Exception &e) {
9841 mx::system_err <<
"acmx2: OnnxWrapper inference error: " << e.what() <<
"\n";
9859 glActiveTexture(GL_TEXTURE0);
9865 if (idx >= 0 &&
library.isCache2D(
static_cast<size_t>(idx)))
9871 static int hdr_counter = 0;
9873 if (received_source_frame) {
9888 library.setUniform(
"history", 0);
9889 glActiveTexture(GL_TEXTURE1);
9890 glBindTexture(GL_TEXTURE_2D_ARRAY,
frame_cache.historyTexture());
9892 const int n_bind =
library.cacheSize();
9893 for (
int i = 0; i < n_bind; ++i) {
9897 library.setUniform(
"samp" + std::to_string(i + 1), i);
9898 glActiveTexture(GL_TEXTURE1 + i);
9899 glBindTexture(GL_TEXTURE_2D,
frame_cache.textureAt(i));
9905#ifdef ACMX2_WITH_CUDA
9906 if (onnxGpuFrameReady) {
9913 if (gpuWorkingBuffer.empty() || gpuWorkingBuffer.cols !=
newFrame.cols || gpuWorkingBuffer.rows !=
newFrame.rows) {
9945 launch_filter(
gpu_filters.data(),
gpu_filters.size(), gpuWorkingBuffer.ptr<
unsigned char>(),
d_ptrList,
gpu_frame_buffer->arraySize, gpuWorkingBuffer.cols, gpuWorkingBuffer.rows, gpuWorkingBuffer.step,
gpu_alpha,
false,
gpu_square_size,
gpu_frame_index,
gpu_frame_dir, &
d_filterList,
gpu_filtersChanged);
9957 glActiveTexture(GL_TEXTURE0);
9962 glActiveTexture(GL_TEXTURE0);
9967 if (!shader_pass_enabled)
9969 for (int idx : shader_pass_list) {
9970 if (idx >= 0 && library.isCache2D(static_cast<size_t>(idx)))
9975 static int counter = 0;
9993 library.setUniform(
"history", 0);
9994 glActiveTexture(GL_TEXTURE1);
9995 glBindTexture(GL_TEXTURE_2D_ARRAY,
frame_cache.historyTexture());
9997 const int n_bind =
library.cacheSize();
9998 for (
int i = 0; i < n_bind; ++i) {
9999 library.setUniform(
"samp" + std::to_string(i + 1), i);
10000 glActiveTexture(GL_TEXTURE1 + i);
10001 glBindTexture(GL_TEXTURE_2D,
frame_cache.textureAt(i));
10007 if (received_source_frame) {
10008 source_frame_ready =
true;
10009 startMediaTimelineIfReady();
10013 if (cache_warmup_frames > 0 && received_source_frame) {
10014 cache_warmup_frames--;
10016 glBindFramebuffer(GL_FRAMEBUFFER, captureFBO);
10017 glViewport(0, 0, win->w, win->h);
10018 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
10020#ifdef AUDIO_ENABLED
10021 if (!isFrozen && audio_is_enabled && media_timeline_started) {
10022 float audio_warmup = updateAudioWarmupEnvelope();
10023 library.setAudioWarmupEnvelope(audio_warmup);
10024 if (file_audio_mode) {
10026 if (process_file_audio) {
10030 mx::system_out <<
"acmx2: Audio file finished, stopping (--audio-trunc).\n";
10035 float spectrum_scale = spectrum_scale_by_sense ? (audio_engine.
analyzer().sensitivity() * audio_warmup) : audio_warmup;
10036 spectrumTex.
update(spectrum_scale);
10037 spectrumTex.
bind();
10038 if (audio_buffer_count > 0) {
10039 spectrumHistory.
update(spectrum_scale);
10040 spectrumHistory.
bind();
10041 library.setSpectrumHistoryHead(spectrumHistory.
newestLayer());
10046 if (!isFrozen && !library.isBypassed()) {
10047 library.useProgram();
10048 library.update(win);
10049 library.setFPS(
static_cast<float>(fps));
10053 const Uint8 *keystate = SDL_GetKeyboardState(NULL);
10054 if (keystate[SDL_SCANCODE_PAGEUP]) {
10055 library.incTimeSpeed(0.1f);
10058 if (keystate[SDL_SCANCODE_PAGEDOWN]) {
10059 library.decTimeSpeed(0.1f);
10063 if (keystate[SDL_SCANCODE_PERIOD]) {
10064 cameraRotationSpeed += 0.5f;
10065 if (cameraRotationSpeed > 50.0f)
10066 cameraRotationSpeed = 50.0f;
10067 mx::system_out <<
"acmx2: Camera rotation speed: " << cameraRotationSpeed <<
"\n";
10070 if (keystate[SDL_SCANCODE_COMMA]) {
10071 cameraRotationSpeed -= 0.5f;
10072 if (cameraRotationSpeed < 0.5f)
10073 cameraRotationSpeed = 0.5f;
10074 mx::system_out <<
"acmx2: Camera rotation speed: " << cameraRotationSpeed <<
"\n";
10079 if (is3d_enabled) {
10080 glEnable(GL_DEPTH_TEST);
10081 glDepthFunc(GL_LESS);
10082 glDepthMask(GL_TRUE);
10083 glDisable(GL_CULL_FACE);
10085 static auto last3DTime = std::chrono::steady_clock::now();
10086 auto now3D = std::chrono::steady_clock::now();
10087 float dt = std::chrono::duration<float>(now3D - last3DTime).count();
10090 last3DTime = now3D;
10092 static float rotation = 0.0f;
10093 rotation = fmod(rotation + 0.5f, 360.0f);
10095 const Uint8 *keystate = SDL_GetKeyboardState(NULL);
10096 if (!oscillateScale) {
10098 if (keystate[SDL_SCANCODE_1]) {
10099 movementSpeed += 0.1f * dt * 30.0f;
10100 mx::system_out <<
"acmx2: camera movement speed increased: " << movementSpeed <<
"\n";
10104 if (keystate[SDL_SCANCODE_2]) {
10105 movementSpeed -= 0.1f * dt * 30.0f;
10106 mx::system_out <<
"acmx2: camera movement speed decreased: " << movementSpeed <<
"\n";
10110 if (keystate[SDL_SCANCODE_EQUALS] || keystate[SDL_SCANCODE_KP_PLUS]) {
10111 cameraDistance += movementSpeed * dt;
10112 mx::system_out <<
"acmx2: cameraDistance increased: " << cameraDistance <<
"\n";
10115 if (keystate[SDL_SCANCODE_MINUS] || keystate[SDL_SCANCODE_KP_MINUS]) {
10116 cameraDistance -= movementSpeed * dt;
10117 mx::system_out <<
"acmx2: cameraDistance decreased: " << cameraDistance <<
"\n";
10120 if (keystate[SDL_SCANCODE_RIGHTBRACKET]) {
10121 modelRenderScale += 0.5f * dt;
10122 mx::system_out <<
"acmx2: Model scale increased to " << modelRenderScale <<
"\n";
10125 if (keystate[SDL_SCANCODE_LEFTBRACKET]) {
10126 modelRenderScale -= 0.5f * dt;
10127 if (modelRenderScale < 0.05f)
10128 modelRenderScale = 0.05f;
10129 mx::system_out <<
"acmx2: Model scale decreased to " << modelRenderScale <<
"\n";
10133 static float t = 0.0f;
10134 float oscOffset = 0.0f;
10135 if (oscillateScale) {
10137 oscOffset = 0.3f * std::sin(t);
10140 if (!viewRotationActive) {
10141 if (keystate[SDL_SCANCODE_W]) {
10142 cameraPitch += cameraRotationSpeed * 0.3f * dt * 30.0f;
10144 if (keystate[SDL_SCANCODE_S]) {
10145 cameraPitch -= cameraRotationSpeed * 0.33f * dt * 30.0f;
10147 cameraPitch = fmod(cameraPitch, 360.0f);
10148 if (cameraPitch < 0.0f)
10149 cameraPitch += 360.0f;
10150 if (keystate[SDL_SCANCODE_A]) {
10151 cameraYaw -= cameraRotationSpeed * 0.3f * dt * 30.0f;
10152 cameraYaw = fmod(cameraYaw + 360.0f, 360.0f);
10154 if (keystate[SDL_SCANCODE_D]) {
10155 cameraYaw += cameraRotationSpeed * 0.3f * dt * 30.0f;
10156 cameraYaw = fmod(cameraYaw, 360.0f);
10159 glm::vec3 lookDirection;
10160 if (viewRotationActive) {
10161 static float viewRotation = 0.0f;
10162 viewRotation = fmod(viewRotation + 0.3f, 360.0f);
10163 float lookX = 0.48f * sin(glm::radians(viewRotation));
10164 float lookY = 0.48f * sin(glm::radians(viewRotation * 0.7f));
10165 float lookZ = 0.48f * cos(glm::radians(viewRotation));
10166 lookDirection = glm::vec3(lookX, lookY, lookZ);
10168 lookDirection.x = cos(glm::radians(cameraPitch)) * cos(glm::radians(cameraYaw));
10169 lookDirection.y = sin(glm::radians(cameraPitch));
10170 lookDirection.z = cos(glm::radians(cameraPitch)) * sin(glm::radians(cameraYaw));
10171 lookDirection = glm::normalize(lookDirection) * 0.48f;
10174 float finalOffset = oscillateScale ? oscOffset : cameraDistance;
10175 glm::vec3 cameraPosBase = glm::vec3(0.0f, 0.0f, 0.0f);
10176 glm::vec3 cameraPos = cameraPosBase - glm::normalize(lookDirection) * finalOffset;
10177 glm::vec3 cameraTarget = cameraPos + lookDirection;
10178 glm::vec3 cameraUp = glm::vec3(0.0f, 1.0f, 0.0f);
10179 if (!viewRotationActive) {
10180 const float pitch = glm::radians(cameraPitch);
10181 const float yaw = glm::radians(cameraYaw);
10182 cameraUp = glm::vec3(-sin(pitch) * cos(yaw), cos(pitch), -sin(pitch) * sin(yaw));
10184 glm::mat4 viewMatrix = glm::lookAt(cameraPos, cameraTarget, cameraUp);
10185 glm::mat4 projectionMatrix = glm::perspective(glm::radians(120.0f),
static_cast<float>(win->w) /
static_cast<float>(win->h), 0.01f, 1000.0f);
10187 glm::mat4 modelMatrix = glm::mat4(1.0f);
10188 modelMatrix = glm::scale(modelMatrix, glm::vec3(modelRenderScale));
10189 modelMatrix = glm::rotate(modelMatrix, glm::radians(modelRotX), glm::vec3(1.0f, 0.0f, 0.0f));
10190 modelMatrix = glm::rotate(modelMatrix, glm::radians(modelRotY), glm::vec3(0.0f, 1.0f, 0.0f));
10191 modelMatrix = glm::rotate(modelMatrix, glm::radians(modelRotZ), glm::vec3(0.0f, 0.0f, 1.0f));
10192 modelMatrix = glm::translate(modelMatrix, modelCenterOffset);
10194 glm::mat4 mvMatrix = viewMatrix * modelMatrix;
10195 GLuint textureForMesh = input_is_hdr ? hdr_linear_video_texture : camera_texture;
10196 if (shader_pass_enabled && !shader_pass_list.empty() && !library.isBypassed()) {
10197 glDisable(GL_DEPTH_TEST);
10198 auto logPassWarning3D = [](
const std::string &msg) {
10199 static Uint64 last_warn_tick = 0;
10200 Uint64 now_tick = SDL_GetTicks64();
10201 if (now_tick - last_warn_tick >= 1000) {
10202 mx::system_out <<
"acmx2: multipass(3D) " << msg <<
"\n";
10204 last_warn_tick = now_tick;
10208 ensurePassTargets(win);
10210 GLuint inputTex = input_is_hdr ? hdr_linear_video_texture : camera_texture;
10212 bool pass_applied =
false;
10213 for (
size_t i = 0; i < shader_pass_list.size(); ++i) {
10214 int shader_idx = shader_pass_list[i];
10215 if (shader_idx >= 0 && shader_idx <
static_cast<int>(library.size2d())) {
10216 gl::ShaderProgram *pass_shader = library.getShader2D(shader_idx);
10218 bool applied =
false;
10219 if (library.isCompute(
static_cast<size_t>(shader_idx))) {
10220 glBindFramebuffer(GL_FRAMEBUFFER, captureFBO);
10221 applied = runComputePass(win,
static_cast<size_t>(shader_idx), inputTex, passTexture[pingpong]);
10223 glBindFramebuffer(GL_FRAMEBUFFER, passFBO[pingpong]);
10224 glViewport(0, 0, win->w, win->h);
10225 glClear(GL_COLOR_BUFFER_BIT);
10226 pass_shader->useProgram();
10227 library.updateShaderUniforms2D(win, shader_idx);
10228 pass_shader->setUniform(
"mv_matrix", glm::mat4(1.0f));
10229 pass_shader->setUniform(
"proj_matrix", glm::mat4(1.0f));
10230 glActiveTexture(GL_TEXTURE0);
10231 glBindTexture(GL_TEXTURE_2D, inputTex);
10232 glUniform1i(glGetUniformLocation(pass_shader->id(),
"samp"), 0);
10233 bindPassHistoryTextures(
static_cast<size_t>(shader_idx));
10234 sprite.setShader(pass_shader);
10235 sprite.setName(
"samp");
10236 sprite.draw(inputTex, 0, 0, win->w, win->h);
10240 pass_applied =
true;
10241 inputTex = passTexture[pingpong];
10242 pingpong = 1 - pingpong;
10245 logPassWarning3D(
"skipping pass index " + std::to_string(shader_idx) +
" (null shader)");
10248 logPassWarning3D(
"skipping invalid pass index " + std::to_string(shader_idx) +
" (valid range 0-" + std::to_string(
static_cast<int>(library.size2d()) - 1) +
")");
10251 if (!pass_applied) {
10252 logPassWarning3D(
"no pass applied; using base camera texture");
10255 textureForMesh = inputTex;
10256 glBindFramebuffer(GL_FRAMEBUFFER, captureFBO);
10257 glViewport(0, 0, win->w, win->h);
10258 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
10259 glEnable(GL_DEPTH_TEST);
10260 glDepthFunc(GL_LESS);
10261 glDepthMask(GL_TRUE);
10264 const bool active_compute = !library.isBypassed() && library.isCompute(library.index());
10265 if (active_compute) {
10266 ensurePassTargets(win);
10267 const int output_index = textureForMesh == passTexture[0] ? 1 : 0;
10268 if (runComputePass(win, library.index(), textureForMesh, passTexture[output_index])) {
10269 textureForMesh = passTexture[output_index];
10271 glBindFramebuffer(GL_FRAMEBUFFER, captureFBO);
10272 glViewport(0, 0, win->w, win->h);
10273 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
10274 glEnable(GL_DEPTH_TEST);
10277 gl::ShaderProgram *activeShader;
10278 if (library.isBypassed() || active_compute) {
10279 activeShader = &fshader3d;
10281 activeShader = library.shader();
10283 activeShader->useProgram();
10284 activeShader->setUniform(
"mv_matrix", mvMatrix);
10285 activeShader->setUniform(
"proj_matrix", projectionMatrix);
10286 glActiveTexture(GL_TEXTURE0);
10287 glBindTexture(GL_TEXTURE_2D, textureForMesh);
10288 glUniform1i(glGetUniformLocation(activeShader->id(),
"samp"), 0);
10289 if (!library.isBypassed()) {
10290 cube.setShaderProgram(activeShader);
10292 cube.setShaderProgram(&fshader3d);
10294 cube.resetToOriginal();
10296 static float amp_x = 0.0f;
10297 static float amp_y = 0.0f;
10298 static float amp_z = 0.0f;
10299 static int dir_x = 1;
10300 static int dir_y = 1;
10301 static int dir_z = 1;
10302 static const float wave_speed = 0.005f;
10303 static const float wave_max = 0.5f;
10304 static const float wave_min = 0.0f;
10305 static float phase = 0.0f;
10307#ifdef AUDIO_ENABLED
10308 if (audio_is_enabled && library.timeAudio())
10309 phase += (library.getAmp() * library.getAmpUntouched());
10315 if (phase > 360.0f)
10318 amp_x += wave_speed * dir_x;
10319 if (amp_x >= wave_max) {
10322 }
else if (amp_x <= wave_min) {
10327 amp_y += wave_speed * dir_y;
10328 if (amp_y >= wave_max) {
10331 }
else if (amp_y <= wave_min) {
10336 amp_z += wave_speed * dir_z;
10337 if (amp_z >= wave_max) {
10340 }
else if (amp_z <= wave_min) {
10344 cube.wave(mx::DeformAxis::X, amp_x, 2.0f, phase);
10345 cube.wave(mx::DeformAxis::Y, amp_y, 2.0f, phase + 120.0f);
10346 cube.wave(mx::DeformAxis::Z, amp_z, 2.0f, phase + 240.0f);
10349 cube.updateBuffers();
10350 cube.recalculateNormals();
10352 for (
auto &m : cube.meshes) {
10355 glFrontFace(GL_CCW);
10357 glDisable(GL_DEPTH_TEST);
10358 GLuint textureForSprite = input_is_hdr ? hdr_linear_video_texture : camera_texture;
10359 if (shader_pass_enabled && !shader_pass_list.empty() && !library.isBypassed()) {
10360 auto logPassWarning2D = [](
const std::string &msg) {
10361 static Uint64 last_warn_tick = 0;
10362 Uint64 now_tick = SDL_GetTicks64();
10363 if (now_tick - last_warn_tick >= 1000) {
10364 mx::system_out <<
"acmx2: multipass(2D) " << msg <<
"\n";
10366 last_warn_tick = now_tick;
10369 ensurePassTargets(win);
10370 glBindFramebuffer(GL_FRAMEBUFFER, captureFBO);
10372 GLuint inputTex = input_is_hdr ? hdr_linear_video_texture : camera_texture;
10374 bool pass_applied =
false;
10376 for (
size_t i = 0; i < shader_pass_list.size(); ++i) {
10377 int shader_idx = shader_pass_list[i];
10378 if (shader_idx >= 0 && shader_idx <
static_cast<int>(library.size())) {
10379 gl::ShaderProgram *pass_shader = library.getShader(shader_idx);
10381 bool applied =
false;
10382 if (library.isCompute(
static_cast<size_t>(shader_idx))) {
10383 glBindFramebuffer(GL_FRAMEBUFFER, captureFBO);
10384 applied = runComputePass(win,
static_cast<size_t>(shader_idx), inputTex, passTexture[pingpong]);
10386 glBindFramebuffer(GL_FRAMEBUFFER, passFBO[pingpong]);
10387 glViewport(0, 0, win->w, win->h);
10388 glClear(GL_COLOR_BUFFER_BIT);
10389 pass_shader->useProgram();
10390 library.updateShaderUniforms(win, shader_idx);
10391 pass_shader->setUniform(
"mv_matrix", glm::mat4(1.0f));
10392 pass_shader->setUniform(
"proj_matrix", glm::mat4(1.0f));
10393 glActiveTexture(GL_TEXTURE0);
10394 glBindTexture(GL_TEXTURE_2D, inputTex);
10395 glUniform1i(glGetUniformLocation(pass_shader->id(),
"samp"), 0);
10396 bindPassHistoryTextures(
static_cast<size_t>(shader_idx));
10397 sprite.setShader(pass_shader);
10398 sprite.setName(
"samp");
10399 sprite.draw(inputTex, 0, 0, win->w, win->h);
10403 pass_applied =
true;
10404 inputTex = passTexture[pingpong];
10405 pingpong = 1 - pingpong;
10408 logPassWarning2D(
"skipping pass index " + std::to_string(shader_idx) +
" (null shader)");
10411 logPassWarning2D(
"skipping invalid pass index " + std::to_string(shader_idx) +
" (valid range 0-" + std::to_string(
static_cast<int>(library.size()) - 1) +
")");
10414 if (!pass_applied) {
10415 logPassWarning2D(
"no pass applied; using base camera texture");
10417 textureForSprite = inputTex;
10419 glBindFramebuffer(GL_FRAMEBUFFER, captureFBO);
10420 glViewport(0, 0, win->w, win->h);
10421 glClear(GL_COLOR_BUFFER_BIT);
10424 const bool active_compute = !library.isBypassed() && library.isCompute(library.index());
10425 if (active_compute) {
10426 ensurePassTargets(win);
10427 const int output_index = textureForSprite == passTexture[0] ? 1 : 0;
10428 if (runComputePass(win, library.index(), textureForSprite, passTexture[output_index])) {
10429 textureForSprite = passTexture[output_index];
10431 glBindFramebuffer(GL_FRAMEBUFFER, captureFBO);
10432 glViewport(0, 0, win->w, win->h);
10433 glClear(GL_COLOR_BUFFER_BIT);
10436 gl::ShaderProgram *activeShader;
10437 if (library.isBypassed() || active_compute) {
10438 activeShader = &fshader;
10440 activeShader = library.shader();
10442 activeShader->useProgram();
10443 activeShader->setUniform(
"mv_matrix", glm::mat4(1.0f));
10444 activeShader->setUniform(
"proj_matrix", glm::mat4(1.0f));
10445 sprite.setShader(activeShader);
10446 sprite.setName(
"samp");
10447 sprite.draw(textureForSprite, 0, 0, win->w, win->h);
10450 if (crossfadeActive) {
10451 applyCrossfade(win, fboTexture);
10452 if (crossfadeActive) {
10453 glBindFramebuffer(GL_FRAMEBUFFER, captureFBO);
10454 glViewport(0, 0, win->w, win->h);
10455 glClear(GL_COLOR_BUFFER_BIT);
10456 fshader.useProgram();
10457 fshader.setUniform(
"mv_matrix", glm::mat4(1.0f));
10458 fshader.setUniform(
"proj_matrix", glm::mat4(1.0f));
10459 sprite.setShader(&fshader);
10460 sprite.setName(
"samp");
10461 sprite.draw(crossfadeTexture, 0, 0, win->w, win->h);
10465#ifdef ACMX2_WITH_DNN
10471 if (human_seg_model && human_background_only && human_overlay_ready && human_overlay_tex != 0 && !input_is_hdr) {
10472 glBindFramebuffer(GL_FRAMEBUFFER, captureFBO);
10473 glViewport(0, 0, win->w, win->h);
10474 glDisable(GL_DEPTH_TEST);
10475 glEnable(GL_BLEND);
10476 glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
10477 fshader.useProgram();
10478 fshader.setUniform(
"mv_matrix", glm::mat4(1.0f));
10479 fshader.setUniform(
"proj_matrix", glm::mat4(1.0f));
10480 sprite.setShader(&fshader);
10481 sprite.setName(
"samp");
10482 sprite.draw(human_overlay_tex, 0, 0, win->w, win->h);
10483 glDisable(GL_BLEND);
10487 glBindFramebuffer(GL_FRAMEBUFFER, 0);
10488 glViewport(0, 0, win->w, win->h);
10489 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
10490 glDisable(GL_DEPTH_TEST);
10494 gl::ShaderProgram *display_shader = &fshader;
10495 int flip_y_uniform = 0;
10496 if (input_is_hdr && display_flip_shader.id() != 0) {
10498 if (!flip_output) {
10499 display_shader = &display_flip_shader;
10500 flip_y_uniform = 1;
10504 display_shader->useProgram();
10505 display_shader->setUniform(
"mv_matrix", glm::mat4(1.0f));
10506 display_shader->setUniform(
"proj_matrix", glm::mat4(1.0f));
10507 if (flip_y_uniform == 1) {
10508 glUniform1i(glGetUniformLocation(display_shader->id(),
"flip_y"), flip_y_uniform);
10510 sprite.setShader(display_shader);
10511 sprite.draw(fboTexture, 0, 0, win->w, win->h);
10519 const bool overlay_will_draw = writer.is_open() && waterFont.handle().has_value() && (display_filter || enableWatermark);
10520 if (overlay_will_draw && preOverlayFBO != 0) {
10521 glBindFramebuffer(GL_READ_FRAMEBUFFER, captureFBO);
10522 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, preOverlayFBO);
10523 glBlitFramebuffer(0, 0, win->w, win->h, 0, 0, win->w, win->h, GL_COLOR_BUFFER_BIT, GL_NEAREST);
10524 glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
10525 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
10528 int watermarkY = 10;
10529 if (display_filter && (writer.is_open() || png_video_mode) && waterFont.handle().has_value()) {
10530 glBindFramebuffer(GL_FRAMEBUFFER, captureFBO);
10531 glViewport(0, 0, win->w, win->h);
10532 glEnable(GL_BLEND);
10533 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
10534 win->text.setColor({255, 0, 255, 255});
10536 const int lineH = waterFontSize + 4;
10537 std::string shaderName = library.getShaderNameByIndex(library.index());
10538 if (!shaderName.empty()) {
10539 win->text.printText_Solid(waterFont, 10, dfY,
"Shader: " + shaderName);
10542 if (shader_pass_enabled && !shader_pass_list.empty()) {
10543 std::string mpLine =
"Multipass: ";
10544 for (
size_t i = 0; i < shader_pass_list.size(); ++i) {
10547 std::string n = library.getShaderNameByIndex(shader_pass_list[i]);
10548 mpLine += n.empty() ? std::to_string(shader_pass_list[i]) : n;
10550 win->text.printText_Solid(waterFont, 10, dfY, mpLine);
10553 if (gpu_filter_enabled && !gpu_filters.empty()) {
10554 std::string gpuLine =
"GPU: ";
10555 for (
size_t i = 0; i < gpu_filters.size(); ++i) {
10558 gpuLine += gpu_filters[i].name;
10560 win->text.printText_Solid(waterFont, 10, dfY, gpuLine);
10563 glDisable(GL_BLEND);
10566 if (enableWatermark && (writer.is_open() || png_video_mode) && waterFont.handle().has_value()) {
10567 glBindFramebuffer(GL_FRAMEBUFFER, captureFBO);
10568 glViewport(0, 0, win->w, win->h);
10569 glEnable(GL_BLEND);
10570 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
10571 win->text.setColor({
static_cast<Uint8
>(watermark_r),
static_cast<Uint8
>(watermark_g),
static_cast<Uint8
>(watermark_b), 255});
10572 win->text.printText_Blended(waterFont, 10, watermarkY, watermark_text);
10573 glDisable(GL_BLEND);
10576 const bool normal_output_frame_due = media_timeline_started && (!file_audio_clock_controls_video || received_source_frame || stop_after_recording_drain);
10577 bool needWriter = (((writer.is_open() || png_video_mode || generate_mode) && normal_output_frame_due) || snapshot_state > 0 || hdr_snapshot_state > 0 || raw_snapshot_state > 0 || tiff_snapshot_state > 0) && !isFrozen;
10579 bool has_snapshot_request = (snapshot_state > 0);
10580 bool has_hdr_snapshot_request = (hdr_snapshot_state > 0);
10581 bool has_raw_snapshot_request = (raw_snapshot_state > 0);
10582 bool has_tiff_snapshot_request = (tiff_snapshot_state > 0);
10583 if (needWriter && input_is_hdr && (writer.is_open() || png_video_mode || generate_mode || has_snapshot_request || has_hdr_snapshot_request || has_raw_snapshot_request || has_tiff_snapshot_request)) {
10596 std::vector<unsigned char> pixels;
10597 hdrReadback(fboTexture, win->w, win->h, pixels);
10601 const int row_bytes = win->w * 8;
10602 std::vector<unsigned char> flipped_pixels(pixels.size());
10603 for (
int y = 0; y < win->h; ++y) {
10604 std::copy(pixels.begin() + y * row_bytes, pixels.begin() + (y + 1) * row_bytes, flipped_pixels.begin() + (win->h - 1 - y) * row_bytes);
10606 pixels = std::move(flipped_pixels);
10609 if (writer.is_open() || png_video_mode || generate_mode || has_hdr_snapshot_request || has_raw_snapshot_request) {
10615 fd.
hdrTrc = input_hdr_trc;
10619 fd.
usesTimelineClock = source_frame_uses_timeline_clock && !has_hdr_snapshot_request && !has_raw_snapshot_request;
10623 std::unique_lock<std::mutex> lock(queueMutex);
10624 queueCondVar.wait(lock, [
this] {
return frameQueue.size() < 30 || !writerRunning; });
10625 frameQueue.push(std::move(fd));
10627 queueCondVar.notify_one();
10630 if (has_tiff_snapshot_request) {
10632 tiff_fd.
pixels = pixels;
10633 tiff_fd.
width = win->w;
10634 tiff_fd.
height = win->h;
10635 tiff_fd.
isHdr =
true;
10636 tiff_fd.
hdrTrc = input_hdr_trc;
10643 std::unique_lock<std::mutex> lock(queueMutex);
10644 queueCondVar.wait(lock, [
this] {
return frameQueue.size() < 30 || !writerRunning; });
10645 frameQueue.push(std::move(tiff_fd));
10647 queueCondVar.notify_one();
10650 if (has_snapshot_request) {
10654 std::vector<unsigned char> sdr_pixels(
static_cast<size_t>(win->w) *
static_cast<size_t>(win->h) * 4);
10655 glBindTexture(GL_TEXTURE_2D, fboTexture);
10656 glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, sdr_pixels.data());
10657 glBindTexture(GL_TEXTURE_2D, 0);
10659 std::vector<unsigned char> flipped_pixels(
static_cast<size_t>(win->w) *
static_cast<size_t>(win->h) * 4);
10660 for (
int y = 0; y < win->h; ++y) {
10661 const int src_row_start = y * win->w * 4;
10662 const int dest_row_start = (win->h - 1 - y) * win->w * 4;
10663 std::copy(sdr_pixels.begin() + src_row_start, sdr_pixels.begin() + src_row_start + (win->w * 4), flipped_pixels.begin() + dest_row_start);
10667 sdr_fd.
pixels = std::move(flipped_pixels);
10668 sdr_fd.
width = win->w;
10670 sdr_fd.
isHdr =
false;
10676 std::unique_lock<std::mutex> lock(queueMutex);
10677 queueCondVar.wait(lock, [
this] {
return frameQueue.size() < 30 || !writerRunning; });
10678 frameQueue.push(std::move(sdr_fd));
10680 queueCondVar.notify_one();
10683 if (has_snapshot_request) {
10684 snapshot_state = 0;
10687 if (has_hdr_snapshot_request) {
10688 hdr_snapshot_state = 0;
10691 if (has_raw_snapshot_request) {
10692 raw_snapshot_state = 0;
10695 if (has_tiff_snapshot_request) {
10696 tiff_snapshot_state = 0;
10698 }
else if (needWriter) {
10700 if (snapshot_state == 1 || hdr_snapshot_state == 1 || raw_snapshot_state == 1 || tiff_snapshot_state == 1) {
10701 glBindBuffer(GL_PIXEL_PACK_BUFFER, pboIds[pboIndex]);
10702 glBindTexture(GL_TEXTURE_2D, fboTexture);
10703 glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
10704 glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
10705 glBindTexture(GL_TEXTURE_2D, 0);
10707 pboIndex = (pboIndex + 1) % 2;
10708 pboNextIndex = (pboNextIndex + 1) % 2;
10709 if (snapshot_state == 1)
10710 snapshot_state = 2;
10711 if (hdr_snapshot_state == 1)
10712 hdr_snapshot_state = 2;
10713 if (raw_snapshot_state == 1)
10714 raw_snapshot_state = 2;
10715 if (tiff_snapshot_state == 1)
10716 tiff_snapshot_state = 2;
10718 bool is_snapshot_frame = (snapshot_state == 2);
10719 bool is_webp_snapshot_frame = (hdr_snapshot_state == 2);
10720 bool is_raw_snapshot_frame = (raw_snapshot_state == 2);
10721 bool is_tiff_snapshot_frame = (tiff_snapshot_state == 2);
10722 const bool has_normal_output = normal_output_frame_due && (writer.is_open() || png_video_mode || generate_mode);
10723 const bool previous_recording_frame_ready = recording_pbo_primed;
10725 glBindBuffer(GL_PIXEL_PACK_BUFFER, pboIds[pboIndex]);
10726 glBindTexture(GL_TEXTURE_2D, fboTexture);
10727 glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
10729 if (has_normal_output) {
10730 recording_pbo_uses_timeline_clock[pboIndex] = source_frame_uses_timeline_clock;
10731 recording_pbo_timeline_frame[pboIndex] = source_timeline_frame;
10732 recording_pbo_primed =
true;
10735 if ((has_normal_output && previous_recording_frame_ready) || is_snapshot_frame || is_webp_snapshot_frame || is_raw_snapshot_frame || is_tiff_snapshot_frame) {
10736 bool used_zero_copy =
false;
10738#ifdef ACMX2_WITH_CUDA
10739 if (writer.is_open() && !generate_mode && !is_snapshot_frame && !is_webp_snapshot_frame && !is_raw_snapshot_frame && !is_tiff_snapshot_frame && recordCudaPboResources[pboNextIndex]) {
10740 cudaGraphicsResource *resource = recordCudaPboResources[pboNextIndex];
10741 void *devPtr =
nullptr;
10742 size_t mappedBytes = 0;
10744 CHECK_CUDA(cudaGraphicsMapResources(1, &resource, 0));
10745 CHECK_CUDA(cudaGraphicsResourceGetMappedPointer(&devPtr, &mappedBytes, resource));
10747 const size_t requiredBytes =
static_cast<size_t>(win->w) *
static_cast<size_t>(win->h) * 4;
10748 if (devPtr && mappedBytes >= requiredBytes) {
10749 if (recording_pbo_uses_timeline_clock[pboNextIndex]) {
10750 used_zero_copy = writer.write_cuda_rgba_at_pts(devPtr,
static_cast<int>(win->w) * 4,
static_cast<int64_t
>(recording_pbo_timeline_frame[pboNextIndex]),
true);
10752 used_zero_copy = writer.write_cuda_rgba(devPtr,
static_cast<int>(win->w) * 4,
true);
10756 CHECK_CUDA(cudaGraphicsUnmapResources(1, &resource, 0));
10760 if (!used_zero_copy) {
10761 glBindBuffer(GL_PIXEL_PACK_BUFFER, pboIds[pboNextIndex]);
10762 GLubyte *src =
static_cast<GLubyte *
>(glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY));
10765 std::vector<unsigned char> pixels(win->w * win->h * 4);
10766 std::memcpy(pixels.data(), src, pixels.size());
10767 glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
10769 std::vector<unsigned char> flipped_pixels(win->w * win->h * 4);
10770 for (
int y = 0; y < win->h; ++y) {
10771 int src_row_start = y * win->w * 4;
10772 int dest_row_start = (win->h - 1 - y) * win->w * 4;
10773 std::copy(pixels.begin() + src_row_start, pixels.begin() + src_row_start + (win->w * 4), flipped_pixels.begin() + dest_row_start);
10777 fd.
pixels = std::move(flipped_pixels);
10780 fd.
isSnapshot = (is_snapshot_frame || is_webp_snapshot_frame);
10785 fd.
timelineFrame = recording_pbo_timeline_frame[pboNextIndex];
10787 if (is_snapshot_frame) {
10788 snapshot_state = 0;
10790 if (is_webp_snapshot_frame) {
10791 hdr_snapshot_state = 0;
10793 if (is_raw_snapshot_frame) {
10794 raw_snapshot_state = 0;
10796 if (is_tiff_snapshot_frame) {
10797 tiff_snapshot_state = 0;
10801 std::unique_lock<std::mutex> lock(queueMutex);
10802 bool is_camera_mode = filename.empty() && graphic.empty();
10803 if (is_camera_mode && !is_snapshot_frame && !is_webp_snapshot_frame && !is_raw_snapshot_frame && !is_tiff_snapshot_frame) {
10804 if (frameQueue.size() > 30) {
10809 queueCondVar.wait(lock, [
this] {
return frameQueue.size() < 30 || !writerRunning; });
10811 frameQueue.push(std::move(fd));
10813 queueCondVar.notify_one();
10817 if (used_zero_copy) {
10818 snapshot_state = 0;
10819 hdr_snapshot_state = 0;
10820 raw_snapshot_state = 0;
10821 tiff_snapshot_state = 0;
10825 glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
10826 glBindTexture(GL_TEXTURE_2D, 0);
10827 pboIndex = (pboIndex + 1) % 2;
10828 pboNextIndex = (pboNextIndex + 1) % 2;
10832 if (stop_after_recording_drain) {
10836 recording_pbo_primed =
false;
10838 emitSilentVideoProgress(
true);
10848 if (overlay_will_draw && preOverlayFBO != 0) {
10849 glBindFramebuffer(GL_READ_FRAMEBUFFER, preOverlayFBO);
10850 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, captureFBO);
10851 glBlitFramebuffer(0, 0, win->w, win->h, 0, 0, win->w, win->h, GL_COLOR_BUFFER_BIT, GL_NEAREST);
10852 glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
10853 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
10856 glBindFramebuffer(GL_FRAMEBUFFER, 0);
10857 glViewport(0, 0, win->w, win->h);
10859 if (!counter_disabled && overlayFont.handle().has_value()) {
10861 auto currentTime = std::chrono::steady_clock::now();
10862 auto fpsDelta = std::chrono::duration_cast<std::chrono::milliseconds>(currentTime - fpsLastTime).count();
10863 if (fpsDelta >= 500) {
10864 displayFPS = (fpsFrameCount * 1000.0) / fpsDelta;
10866 fpsLastTime = currentTime;
10869 std::string timerStr = getTimeString();
10870 std::ostringstream fpsStr;
10871 fpsStr << std::fixed << std::setprecision(1) << displayFPS <<
" FPS";
10872 glEnable(GL_BLEND);
10873 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
10874 win->text.setColor({0, 0, 255, 255});
10875 win->text.printText_Blended(overlayFont, 10, 10, cached_shader_name);
10877#ifdef AUDIO_ENABLED
10880 const std::string trackName = std::filesystem::path(trackPath).filename().string();
10881 if (!trackName.empty()) {
10882 win->text.setColor({255, 0, 255, 255});
10883 win->text.printText_Blended(overlayFont, 10, overlayY,
"Track: " + trackName);
10888 if (gpu_filter_enabled && !gpu_filters.empty()) {
10889 win->text.setColor({255, 0, 255, 255});
10890 std::string gpuLine =
"GPU: ";
10891 for (
size_t i = 0; i < gpu_filters.size(); ++i) {
10894 gpuLine += gpu_filters[i].name;
10896 win->text.printText_Blended(overlayFont, 10, overlayY, gpuLine);
10899 if (autopilot_enabled) {
10900 const int activeInterval = autopilot_random_interval ? autopilot_interval_frames : autopilot_frames;
10901 const int remainingFrames = std::max(0, activeInterval - autopilot_counter);
10902 const int playlistCount = activePlaylistSize();
10903 std::ostringstream autopilotLine;
10904 if (autopilot_random_interval) {
10905 autopilotLine <<
"Autopilot " << (autopilot_sequential ?
"seq" :
"rnd") <<
" [4-" << std::max(4, autopilot_random_timeout) <<
"] cur=" << activeInterval <<
" next=" << remainingFrames <<
"f";
10907 autopilotLine <<
"Autopilot " << (autopilot_sequential ?
"seq" :
"rnd") <<
" every " << activeInterval <<
"f next=" << remainingFrames <<
"f";
10909 if (playlistCount > 0) {
10910 autopilotLine <<
" idx=" << (playlist_index + 1) <<
"/" << playlistCount;
10912 win->text.setColor({0, 255, 255, 255});
10913 win->text.printText_Blended(overlayFont, 10, overlayY, autopilotLine.str());
10916 if (!crossfadeShaderNames.empty()) {
10917 int n =
static_cast<int>(crossfadeShaderNames.size());
10918 std::string xfadeLine =
"XFade [" + std::to_string(crossfadeShaderIndex + 1) +
"/" + std::to_string(n) +
"]: " + crossfadeShaderNames[crossfadeShaderIndex];
10919 win->text.setColor({255, 200, 0, 255});
10920 win->text.printText_Blended(overlayFont, 10, overlayY, xfadeLine);
10923 win->text.setColor({255, 255, 255, 255});
10924 win->text.printText_Blended(overlayFont, 10, overlayY, timerStr);
10925 win->text.printText_Blended(overlayFont, 10, overlayY + 30, fpsStr.str());
10926 win->text.setColor({128, 128, 128, 255});
10927 win->text.printText_Blended(overlayFont, 10, overlayY + 55,
"F9: Toggle overlay");
10929 drawMidiOverlay(win, overlayFont, overlayY + 80);
10931 glDisable(GL_BLEND);
10934 static auto lastUpdate = std::chrono::steady_clock::now();
10935 auto now = std::chrono::steady_clock::now();
10937 if (!graphic.empty()) {
10939 emitSilentGraphicsProgress(
false);
10940 }
else if (std::chrono::duration_cast<std::chrono::milliseconds>(now - lastUpdate).count() >= 250) {
10941 std::string timeStr = getTimeString();
10942 int64_t currentFrames = getFrameCount();
10944 std::ostringstream stream;
10945 stream <<
"ACMX2 - Graphics Mode - " << timeStr <<
" [" << currentFrames <<
" frames]";
10946 appendRecordingTitleSuffix(stream);
10947 win->setWindowTitle(stream.str());
10951 }
else if (!filename.empty()) {
10952 if (use_ffmpeg_reader) {
10953 frame_counter =
static_cast<unsigned int>(std::max<int64_t>(0, ffmpeg_reader.getCurrentFrame()));
10954 }
else if (cap.isOpened()) {
10955 frame_counter =
static_cast<unsigned int>(cap.get(cv::CAP_PROP_POS_FRAMES));
10958 if (silent_mode && (totalFrames > 0.0 || (duration_limit > 0.0 && fps > 0.0))) {
10959 emitSilentVideoProgress(
false);
10960 }
else if (silent_mode) {
10966 static auto lastProgressEmitUnk = std::chrono::steady_clock::now();
10967 if (std::chrono::duration_cast<std::chrono::milliseconds>(now - lastProgressEmitUnk).count() >= 500) {
10968 lastProgressEmitUnk = now;
10969 int64_t frames_written = png_video_mode ?
static_cast<int64_t
>(png_video_frame_counter.load()) : (writer.is_open() ? writer.get_frame_count() : 0);
10970 double elapsed_secs = (fps > 0.0) ?
static_cast<double>(frame_counter) / fps : 0.0;
10971 uint64_t hours =
static_cast<uint64_t
>(elapsed_secs / 3600);
10972 uint64_t minutes =
static_cast<uint64_t
>(elapsed_secs / 60) % 60;
10973 uint64_t seconds =
static_cast<uint64_t
>(elapsed_secs) % 60;
10975 std::cout <<
"acmx2: [ ?%] "
10976 <<
"Frame " << frame_counter <<
"/?"
10977 <<
" | Written: " << frames_written <<
" | Time: " << std::setfill(
'0') << std::setw(2) << hours <<
":" << std::setfill(
'0') << std::setw(2) << minutes <<
":" << std::setfill(
'0') << std::setw(2) << seconds << std::setfill(
' ');
10978 appendSilentProgressFileSize(std::cout);
10979 std::cout <<
"\n" << std::flush;
10983 if (!silent_mode && std::chrono::duration_cast<std::chrono::seconds>(now - lastUpdate).count() >= 1) {
10984 if (totalFrames <= 0.0 && !use_ffmpeg_reader && cap.isOpened()) {
10985 totalFrames = cap.get(cv::CAP_PROP_FRAME_COUNT);
10987 std::string timeStr = getTimeString();
10988 std::ostringstream stream;
10989 stream <<
"ACMX2 - [" << frame_counter <<
"/" <<
static_cast<int>(totalFrames) <<
"] - " << timeStr <<
" - Video Mode";
10990 appendRecordingTitleSuffix(stream);
10991 win->setWindowTitle(stream.str());
10995 }
else if (cap.isOpened() && filename.empty()) {
10996 if (std::chrono::duration_cast<std::chrono::seconds>(now - lastUpdate).count() >= 1) {
10997 std::string timeStr = getTimeString();
10998 int64_t currentFrames = getFrameCount();
10999 std::ostringstream stream;
11000 stream <<
"ACMX2 - Capture Mode - " << timeStr <<
" [" << currentFrames <<
" frames]";
11001 appendRecordingTitleSuffix(stream);
11002 win->setWindowTitle(stream.str());
11006 const bool autopilotTickEnabled = autopilot_random_interval || autopilot_frames > 0;
11007 if (playlist_enabled && autopilot_enabled && autopilotTickEnabled) {
11008 const int interval = autopilot_random_interval ? autopilot_interval_frames : autopilot_frames;
11009 if (++autopilot_counter >= interval) {
11010 autopilot_counter = 0;
11011 if (autopilot_sequential && !autopilot_random_interval) {
11012 autopilotSequentialAdvance(win);
11014 autopilotRandomSwitch(win);
11016 if (autopilot_random_interval) {
11017 resetAutopilotInterval();
11041 const uint64_t duration_frames = std::max<uint64_t>(1,
static_cast<uint64_t
>(std::ceil(
duration_limit *
fps)));
11042 if (
repeat || expected_frames == 0) {
11043 expected_frames = duration_frames;
11045 expected_frames = std::min(expected_frames, duration_frames);
11048 if (expected_frames == 0) {
11053 const uint64_t processed_frames = complete ? expected_frames : std::min(current_frame, expected_frames);
11054 int current_percent =
static_cast<int>((
static_cast<double>(processed_frames) / expected_frames) * 100.0);
11056 current_percent = std::min(current_percent, 99);
11059 const auto now = std::chrono::steady_clock::now();
11062 if (!complete && !percent_changed && !time_elapsed) {
11069 const double elapsed_secs =
static_cast<double>(processed_frames) /
fps;
11070 const uint64_t hours =
static_cast<uint64_t
>(elapsed_secs / 3600.0);
11071 const uint64_t minutes =
static_cast<uint64_t
>(elapsed_secs / 60.0) % 60;
11072 const uint64_t seconds =
static_cast<uint64_t
>(elapsed_secs) % 60;
11074 std::cout <<
"acmx2: [" << std::setw(3) << current_percent <<
"%] "
11075 <<
"Frame " << processed_frames <<
"/" << expected_frames <<
" | Written: " << frames_written <<
" | Time: " << std::setfill(
'0') << std::setw(2) << hours <<
":" << std::setw(2) << minutes <<
":" << std::setw(2) << seconds << std::setfill(
' ');
11077 std::cout <<
"\n" << std::flush;
11094 const uint64_t expected_frames = std::max<uint64_t>(1,
static_cast<uint64_t
>(std::ceil(
duration_limit *
fps)));
11095 const uint64_t processed_frames = complete ? expected_frames : std::min<uint64_t>(
static_cast<uint64_t
>(
frame_counter) + 1, expected_frames);
11096 const uint64_t frame_interval = std::max<uint64_t>(1,
static_cast<uint64_t
>(std::ceil(
fps)));
11097 const auto now = std::chrono::steady_clock::now();
11101 if (!complete && !frame_interval_elapsed && !time_interval_elapsed) {
11105 int percent =
static_cast<int>((
static_cast<double>(processed_frames) / expected_frames) * 100.0);
11107 percent = std::min(percent, 99);
11110 const double elapsed_secs =
static_cast<double>(processed_frames) /
fps;
11111 const uint64_t hours =
static_cast<uint64_t
>(elapsed_secs / 3600.0);
11112 const uint64_t minutes =
static_cast<uint64_t
>(elapsed_secs / 60.0) % 60;
11113 const uint64_t seconds =
static_cast<uint64_t
>(elapsed_secs) % 60;
11114 const int64_t frames_written =
writer.is_open() ?
writer.get_frame_count() : 0;
11116 std::cout <<
"acmx2: [" << std::setw(3) << percent <<
"%] "
11117 <<
"Frame " << processed_frames <<
"/" << expected_frames <<
" | Written: " << frames_written <<
" | Time: " << std::setfill(
'0') << std::setw(2) << hours <<
":" << std::setw(2) << minutes <<
":" << std::setw(2) << seconds << std::setfill(
' ');
11119 std::cout <<
"\n" << std::flush;
11135 int64_t frameCount = 0;
11136 double timeSeconds = 0.0;
11139 frameCount =
writer.get_frame_count();
11140 timeSeconds = (
fps > 0.0) ?
static_cast<double>(frameCount) /
fps : 0.0;
11143 timeSeconds = (
fps > 0.0) ?
static_cast<double>(frameCount) /
fps : 0.0;
11146 uint64_t hours =
static_cast<uint64_t
>(timeSeconds / 3600);
11147 uint64_t minutes =
static_cast<uint64_t
>(timeSeconds / 60) % 60;
11148 uint64_t seconds =
static_cast<uint64_t
>(timeSeconds) % 60;
11150 std::ostringstream timerStr;
11153 double percentage = (currentFrame /
totalFrames) * 100.0;
11154 timerStr << std::fixed << std::setprecision(1) << percentage <<
"% - ";
11157 timerStr << std::setfill(
'0') << std::setw(2) << hours <<
":" << std::setfill(
'0') << std::setw(2) << minutes <<
":" << std::setfill(
'0') << std::setw(2) << seconds;
11158 return timerStr.str();
11166 stream <<
" (Recording)";
11168 constexpr double kBytesPerMB = 1024.0 * 1024.0;
11169 const double file_size_mb =
static_cast<double>(*file_size_bytes) / kBytesPerMB;
11170 stream <<
" [File: " << std::fixed << std::setprecision(2) << file_size_mb <<
" MB]";
11176 return std::nullopt;
11179 std::error_code file_error;
11180 if (!std::filesystem::is_regular_file(
ofilename, file_error) || file_error) {
11181 return std::nullopt;
11183 const std::uintmax_t file_size = std::filesystem::file_size(
ofilename, file_error);
11185 return std::nullopt;
11194 if (!file_size_bytes.has_value()) {
11198 constexpr double kBytesPerMB = 1024.0 * 1024.0;
11199 const double file_size_mb =
static_cast<double>(*file_size_bytes) / kBytesPerMB;
11200 std::ostringstream size_stream;
11201 size_stream << std::fixed << std::setprecision(2) << file_size_mb;
11202 stream <<
" | Size: " << size_stream.str() <<
" MB";
11214 return writer.get_frame_count();
11253 virtual void event(gl::GLWindow *win, SDL_Event &e)
override {
11256 switch (e.key.keysym.sym) {
11263 mx::system_out <<
"acmx2: Post-shader (Shift+Up): " <<
library.getFullShaderName() <<
"\n";
11283 mx::system_out <<
"acmx2: Playlist Node: " << node.name <<
" [" << node.shader_indices.size() <<
" shaders] (" << (
playlist_index + 1) <<
"/" <<
playlist_tree.size() <<
")\n";
11311 mx::system_out <<
"acmx2: Post-shader (Shift+Down): " <<
library.getFullShaderName() <<
"\n";
11331 mx::system_out <<
"acmx2: Playlist Node: " << node.name <<
" [" << node.shader_indices.size() <<
" shaders] (" << (
playlist_index + 1) <<
"/" <<
playlist_tree.size() <<
")\n";
11388 if (playlist_index < 0 || playlist_index >=
static_cast<int>(
playlist_tree.size()))
11397 mx::system_out <<
"acmx2: Playlist mode enabled - Node: " << node.name <<
" [" << node.shader_indices.size() <<
" shaders] (" << (
playlist_index + 1) <<
"/" <<
playlist_tree.size() <<
")\n";
11406 mx::system_out <<
"acmx2: Playlist mode disabled - restored original" << (
shader_pass_enabled ?
" multi-pass" :
" single shader") <<
"\n";
11420 mx::system_out <<
"acmx2: Playlist mode enabled [" <<
playlist_indices.size() <<
" shaders]\n";
11423 mx::system_out <<
"acmx2: Playlist mode disabled\n";
11428 mx::system_out <<
"acmx2: paused: " << ((
isPaused ==
true) ?
"enabled" :
"disabled") <<
"\n";
11436 mx::system_out <<
"acmx2: frozen: " << ((
isFrozen ==
true) ?
"enabled" :
"disabled") <<
"\n";
11443 mx::system_out <<
"acmx2: Shader lock: " << (
shaderLocked ?
"enabled" :
"disabled") <<
"\n";
11448 mx::system_out <<
"acmx2: Autopilot requires playlist mode (press P first)\n";
11453 mx::system_out <<
"acmx2: Autopilot has no playlist entries\n";
11470 mx::system_out <<
"acmx2: Autopilot enabled (random) (every " <<
autopilot_frames <<
" frames)\n";
11473 mx::system_out <<
"acmx2: Autopilot disabled\n";
11479 mx::system_out <<
"acmx2: Sequential autopilot requires playlist mode (press P first)\n";
11484 mx::system_out <<
"acmx2: Sequential autopilot has no playlist entries\n";
11491 mx::system_out <<
"acmx2: Autopilot disabled\n";
11503 mx::system_out <<
"acmx2: Autopilot enabled (sequential) (every " <<
autopilot_frames <<
" frames)\n";
11519#ifdef ACMX2_WITH_TIFF
11535#ifdef AUDIO_ENABLED
11548 mx::system_out <<
"acmx2: View rotation: " << (
viewRotationActive ?
"enabled" :
"disabled") <<
"\n";
11552 if ((e.key.keysym.mod & KMOD_CTRL) != 0) {
11554 mx::system_out <<
"acmx2: Ctrl+X pressed, exiting without audio mux\n";
11560 mx::system_out <<
"acmx2: Camera distance reset\n";
11565 mx::system_out <<
"acmx2: Scale oscillation " << (
oscillateScale ?
"enabled" :
"disabled") <<
"\n";
11570 mx::system_out <<
"acmx2: Wave effect " << (
waveActive ?
"enabled" :
"disabled") <<
"\n";
11575 mx::system_out <<
"acmx2: Watermark " << (
enableWatermark ?
"enabled" :
"disabled") <<
"\n";
11582 mx::system_out <<
"acmx2: Multi-shader pass " << (
shader_pass_enabled ?
"enabled" :
"disabled") <<
"\n";
11585 mx::system_out <<
"acmx2: No shader pass list defined (use --shader-pass)\n";
11591 mx::system_out <<
"acmx2: Cannot switch to 3D mode - 3D shaders not compiled (use --enable-3d at startup)\n";
11593 }
else if (
cube.meshes.empty()) {
11594 mx::system_out <<
"acmx2: Cannot switch to 3D mode - no model loaded (use --enable-3d)\n";
11600 mx::system_out <<
"acmx2: " << (
is3d_enabled ?
"3D" :
"2D") <<
" mode " << (
is3d_enabled ?
"enabled" :
"disabled") <<
"\n";
11605 if (std::get<0>(
flib) == 0) {
11606 mx::system_out <<
"acmx2: Random multipass mode not available in single shader mode\n";
11614 mx::system_out <<
"acmx2: Random multipass mode enabled\n";
11626 mx::system_out <<
"acmx2: Random multipass mode disabled - restored original\n";
11634 mx::system_out <<
"acmx2: Press R first to enable random multipass mode\n";
11642 mx::system_out <<
"acmx2: Press R first to enable random multipass mode\n";
11650 mx::system_out <<
"acmx2: Press R first to enable random multipass mode\n";
11657 switch (e.key.keysym.sym) {
11664#ifdef AUDIO_ENABLED
11665 case SDLK_INSERT: {
11670 mx::system_out <<
"acmx2: Audio sensitivity increased to " << s <<
"\n";
11674 case SDLK_DELETE: {
11679 mx::system_out <<
"acmx2: Audio sensitivity decreased to " << s <<
"\n";
11685 mx::system_out <<
"acmx2: Spectrum sensitivity scaling " << (
spectrum_scale_by_sense ?
"enabled" :
"disabled") <<
"\n";
11691 mx::system_out <<
"acmx2: Overlay " << (
counter_disabled ?
"hidden" :
"shown") <<
" (F9)\n";
11694 case SDLK_LEFTBRACKET:
11702 case SDLK_RIGHTBRACKET:
11723 std::tuple<int, std::string, int>
flib;
11725#ifdef AUDIO_ENABLED
11795 std::chrono::steady_clock::time_point
lastFrameTime = std::chrono::steady_clock::now();
11875 for (
int i = 0; i < 2; i++) {
11876 glBindBuffer(GL_PIXEL_PACK_BUFFER,
pboIds[i]);
11877 GLubyte *src =
static_cast<GLubyte *
>(glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY));
11880 std::vector<unsigned char> pixels(win->w * win->h * 4);
11881 std::memcpy(pixels.data(), src, pixels.size());
11882 glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
11884 std::vector<unsigned char> flipped_pixels(win->w * win->h * 4);
11885 for (
int y = 0; y < win->h; ++y) {
11886 int src_row_start = y * win->w * 4;
11887 int dest_row_start = (win->h - 1 - y) * win->w * 4;
11888 std::copy(pixels.begin() + src_row_start, pixels.begin() + src_row_start + (win->w * 4), flipped_pixels.begin() + dest_row_start);
11892 fd.
pixels = std::move(flipped_pixels);
11898 std::lock_guard<std::mutex> lock(
queueMutex);
11903 glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
11924 glBindFramebuffer(GL_FRAMEBUFFER,
captureFBO);
11928 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE,
nullptr);
11930 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
11931 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
11933 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
fboTexture, 0);
11936 glBindRenderbuffer(GL_RENDERBUFFER,
depthBuffer);
11937 glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, width, height);
11938 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER,
depthBuffer);
11940 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
11941 glBindFramebuffer(GL_FRAMEBUFFER, 0);
11942 throw mx::Exception(
"FBO is not complete.");
11944 glBindFramebuffer(GL_FRAMEBUFFER, 0);
11950 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE,
nullptr);
11951 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
11952 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
11953 glBindTexture(GL_TEXTURE_2D, 0);
11959 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
preOverlayTexture, 0);
11960 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
11961 glBindFramebuffer(GL_FRAMEBUFFER, 0);
11962 throw mx::Exception(
"preOverlayFBO is not complete.");
11964 glBindFramebuffer(GL_FRAMEBUFFER, 0);
11978 GLuint texture = 0;
11979 glGenTextures(1, &texture);
11980 GLenum error = glGetError();
11981 if (error != GL_NO_ERROR) {
11982 throw mx::Exception(
"OpenGL error: glGenTextures() returned " + std::to_string(error));
11985 glBindTexture(GL_TEXTURE_2D, texture);
11986 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
11987 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
11988 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
11989 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
11992 cv::cvtColor(frame, temp, cv::COLOR_BGR2RGBA);
11993 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, temp.cols, temp.rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, temp.ptr());
11995 error = glGetError();
11996 if (error != GL_NO_ERROR) {
11997 throw mx::Exception(
"OpenGL error: glTexImage2D() returned " + std::to_string(error));
11999 glBindTexture(GL_TEXTURE_2D, 0);
12014 glBindTexture(GL_TEXTURE_2D, texture);
12016 cv::cvtColor(frame, temp, cv::COLOR_BGR2RGBA);
12017 GLint texWidth = 0, texHeight = 0;
12018 glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &texWidth);
12019 glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &texHeight);
12020 if (texWidth != temp.cols || texHeight != temp.rows) {
12021 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, temp.cols, temp.rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, temp.ptr());
12023 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, temp.cols, temp.rows, GL_RGBA, GL_UNSIGNED_BYTE, temp.ptr());
12025 glBindTexture(GL_TEXTURE_2D, 0);
12038 glBindTexture(GL_TEXTURE_2D, texture);
12039 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, frame.cols, frame.rows, GL_RGBA, GL_UNSIGNED_BYTE, frame.ptr());
12040 glBindTexture(GL_TEXTURE_2D, 0);
12071 if (!
cap.read(drain) || drain.empty())
12077 cv::Mat localFrame;
12078 if (!
cap.read(localFrame)) {
12079 mx::system_err <<
"acmx2: camera read failed.\n";
12084 if (localFrame.empty()) {
12087 cv::flip(localFrame, localFrame, 0);
12097 }
catch (
const std::exception &e) {
12098 mx::system_err <<
"acmx2: Capture thread exception: " << e.what() <<
"\n";
12141 auto write_video_frame = [
this](
const FrameData &fd) {
12145 std::ostringstream frame_name;
12146 frame_name <<
png_video_dir <<
"/frame-" << std::setfill(
'0') << std::setw(8) << frame_index <<
".png";
12147 std::string frame_path = frame_name.str();
12149 png::SavePNG_RGBA16(frame_path.c_str(), fd.
pixels.data(), fd.
width, fd.
height);
12151 png::SavePNG_RGBA(frame_path.c_str(),
const_cast<unsigned char *
>(fd.
pixels.data()), fd.
width, fd.
height);
12153 }
else if (fd.
isHdr) {
12157 writer.write_hdr_rgba16(
const_cast<unsigned char *
>(fd.
pixels.data()));
12163 writer.write(
const_cast<unsigned char *
>(fd.
pixels.data()));
12168 writer.write(
const_cast<unsigned char *
>(fd.
pixels.data()));
12176 std::ostringstream frame_name;
12177 frame_name <<
generate_dir <<
"/frame-" << std::setfill(
'0') << std::setw(8) << saved_idx <<
".png";
12178 std::string frame_path = frame_name.str();
12180 png::SavePNG_RGBA16(frame_path.c_str(), fd.
pixels.data(), fd.
width, fd.
height);
12182 png::SavePNG_RGBA(frame_path.c_str(),
const_cast<unsigned char *
>(fd.
pixels.data()), fd.
width, fd.
height);
12194 std::unique_lock<std::mutex> lock(
queueMutex);
12211 auto now1 = std::chrono::system_clock::now();
12212 std::time_t now_c = std::chrono::system_clock::to_time_t(now1);
12213 std::tm localTime{};
12215 localtime_s(&localTime, &now_c);
12217 localtime_r(&now_c, &localTime);
12220 std::ostringstream oss;
12221 oss << std::put_time(&localTime,
"%Y.%m.%d-%H.%M.%S");
12222 std::string snapshot_type = fd.
isHdr ?
"ACMX2.HDR.Snapshot" :
"ACMX2.Snapshot";
12223#ifdef ACMX2_WITH_WEBP
12225 const char *snap_ext = write_webp ?
".webp" :
".png";
12227 const char *snap_ext =
".png";
12229 std::string name = snap_prefix +
"/" + snapshot_type +
"-" + oss.str() +
"-" + std::to_string(fd.
width) +
"x" + std::to_string(fd.
height) +
"-" + std::to_string(current_offset) + snap_ext;
12232#ifdef ACMX2_WITH_WEBP
12234 mx::system_err <<
"acmx2: ERROR: failed to write HDR WebP snapshot: " << name <<
"\n";
12241#ifdef ACMX2_WITH_WEBP
12242 if (!saveSdrWebPFromRgba8(name.c_str(), fd.
pixels.data(), fd.
width, fd.
height)) {
12243 mx::system_err <<
"acmx2: ERROR: failed to write SDR WebP snapshot: " << name <<
"\n";
12246 png::SavePNG_RGBA(name.c_str(),
const_cast<unsigned char *
>(fd.
pixels.data()), fd.
width, fd.
height);
12249 png::SavePNG_RGBA(name.c_str(),
const_cast<unsigned char *
>(fd.
pixels.data()), fd.
width, fd.
height);
12253 mx::system_out <<
"acmx2: Took snapshot: " << name <<
"\n";
12261 auto now1 = std::chrono::system_clock::now();
12262 std::time_t now_c = std::chrono::system_clock::to_time_t(now1);
12263 std::tm localTime{};
12265 localtime_s(&localTime, &now_c);
12267 localtime_r(&now_c, &localTime);
12269 std::ostringstream oss;
12270 oss << std::put_time(&localTime,
"%Y.%m.%d-%H.%M.%S");
12271 std::string snapshot_type = fd.
isHdr ?
"ACMX2.HDR.Snapshot" :
"ACMX2.Raw";
12272 std::string name = snap_prefix +
"/" + snapshot_type +
"-" + oss.str() +
"-" + std::to_string(fd.
width) +
"x" + std::to_string(fd.
height) +
"-" + std::to_string(current_offset) +
".raw";
12274 size_t bpp = fd.
isHdr ? 8 : 4;
12275 png::SaveRawBytes(name.c_str(), fd.
pixels.data(),
static_cast<size_t>(fd.
width),
static_cast<size_t>(fd.
height), bpp);
12277 mx::system_out <<
"acmx2: Saved raw frame: " << name <<
"\n";
12281#ifdef ACMX2_WITH_TIFF
12286 auto now1 = std::chrono::system_clock::now();
12287 std::time_t now_c = std::chrono::system_clock::to_time_t(now1);
12288 std::tm localTime{};
12290 localtime_s(&localTime, &now_c);
12292 localtime_r(&now_c, &localTime);
12294 std::ostringstream oss;
12295 oss << std::put_time(&localTime,
"%Y.%m.%d-%H.%M.%S");
12296 std::string snapshot_type = fd.
isHdr ?
"ACMX2.HDR.Snapshot" :
"ACMX2.Snapshot";
12297 std::string name = snap_prefix +
"/" + snapshot_type +
"-" + oss.str() +
"-" + std::to_string(fd.
width) +
"x" + std::to_string(fd.
height) +
"-" + std::to_string(current_offset) +
".tiff";
12302 ok = saveSdrTiffFromRgba8(name.c_str(), fd.
pixels.data(), fd.
width, fd.
height);
12305 mx::system_err <<
"acmx2: ERROR: failed to write TIFF snapshot: " << name <<
"\n";
12307 mx::system_out <<
"acmx2: Took snapshot: " << name <<
"\n";
12314 if (is_snapshot_task) {
12318 write_video_frame(fd);
12320 }
catch (
const std::exception &e) {
12321 mx::system_err <<
"acmx2: writer thread exception: " << e.what() <<
"\n";
12334#ifdef AUDIO_ENABLED
12338 mx::system_err <<
"acmx2: Error could not start audio recording to: " <<
audio_record_file <<
"\n";
12359#ifdef AUDIO_ENABLED
12363 mx::system_out <<
"acmx2: Media timeline started on first source frame\n";
12375#ifdef AUDIO_ENABLED
12390#ifdef AUDIO_ENABLED
12412#ifdef AUDIO_ENABLED
12417 mx::system_out <<
"acmx2: recorded audio file not found, skipping recorded-audio mux: " <<
audio_record_file <<
"\n";
12421 if (recorder.is_recording())
12423 std::string out_ext = std::filesystem::path(
ofilename).extension().string();
12424 if (out_ext.empty())
12426 std::string tmp_out =
ofilename +
".tmp" + out_ext;
12427 bool is_mp4_like = (out_ext ==
".mp4" || out_ext ==
".MP4" || out_ext ==
".mov" || out_ext ==
".MOV" || out_ext ==
".m4v" || out_ext ==
".M4V");
12428 int64_t fc =
writer.get_frame_count();
12430 mx::system_err <<
"acmx2: no encoded video frames; skipping recorded-audio mux for: " <<
ofilename <<
"\n";
12434 double video_duration = (
fps > 0.0 && fc > 0) ?
static_cast<double>(fc) /
fps : 0.0;
12440 const double audio_duration = recorder.duration_seconds();
12441 double itsscale = 1.0;
12443 if (!timestamped_webcam && video_duration > 0.0 && audio_duration > 0.0) {
12444 const double s = audio_duration / video_duration;
12445 if (s >= 0.5 && s <= 2.0) {
12449 const bool apply_itsscale = std::abs(itsscale - 1.0) > 0.001;
12450 std::ostringstream cmd;
12451 cmd <<
"ffmpeg -y";
12452 if (apply_itsscale) {
12453 cmd <<
" -itsscale " << std::fixed << std::setprecision(6) << itsscale;
12456 <<
" -c:v copy -c:a aac -b:a 192k";
12457 if (!apply_itsscale && video_duration > 0.0) {
12460 cmd <<
" -t " << std::fixed << std::setprecision(3) << video_duration;
12463 cmd <<
" -movflags +faststart";
12465 cmd <<
" \"" << tmp_out <<
"\" 2>&1";
12466 mx::system_out <<
"acmx2: muxing recorded audio into video";
12467 if (apply_itsscale) {
12468 mx::system_out <<
" (A/V resync itsscale=" << std::fixed << std::setprecision(4) << itsscale <<
", video=" << std::setprecision(3) << video_duration <<
"s, audio=" << audio_duration <<
"s)";
12470 mx::system_out <<
"...\n";
12472 int ret = std::system(cmd.str().c_str());
12475 std::rename(tmp_out.c_str(),
ofilename.c_str());
12478 mx::system_out <<
"acmx2: removed temporary recorded audio: " <<
audio_record_file <<
"\n";
12480 mx::system_err <<
"acmx2: warning could not remove recorded audio file: " <<
audio_record_file <<
"\n";
12483 mx::system_err <<
"acmx2: ffmpeg mux failed (exit code " << ret <<
")\n";
12484 std::remove(tmp_out.c_str());
12503#ifdef AUDIO_ENABLED
12506 std::string out_ext = std::filesystem::path(
ofilename).extension().string();
12507 if (out_ext.empty())
12509 std::string tmp_out =
ofilename +
".tmp" + out_ext;
12510 bool is_mp4_like = (out_ext ==
".mp4" || out_ext ==
".MP4" || out_ext ==
".mov" || out_ext ==
".MOV" || out_ext ==
".m4v" || out_ext ==
".M4V");
12511 int64_t fc =
writer.get_frame_count();
12513 mx::system_err <<
"acmx2: no encoded video frames; skipping file-audio mux for: " <<
ofilename <<
"\n";
12517 double video_duration = (
fps > 0.0 && fc > 0) ?
static_cast<double>(fc) /
fps : 0.0;
12518 double mux_duration = video_duration;
12521 mux_duration = mux_duration > 0.0 ? std::min(mux_duration, source_video_duration) : source_video_duration;
12523 std::ostringstream cmd;
12524 cmd <<
"ffmpeg -y -i \"" <<
ofilename <<
"\"";
12526 cmd <<
" -stream_loop -1";
12529 std::filesystem::path concatPath;
12530 if (audioSources.size() > 1) {
12531 concatPath = std::filesystem::temp_directory_path() / (
"acmx2-audio-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()) +
".ffconcat");
12532 std::ofstream concatFile(concatPath);
12533 concatFile <<
"ffconcat version 1.0\n";
12534 for (
const std::string &source : audioSources) {
12535 std::string escapedSource;
12536 for (
char value : source) {
12538 escapedSource +=
"'\\''";
12540 escapedSource += value;
12542 concatFile <<
"file '" << escapedSource <<
"'\n";
12544 concatFile.close();
12545 cmd <<
" -f concat -safe 0 -i \"" << concatPath.string() <<
"\"";
12547 const std::string audioSource = audioSources.empty() ?
audio_file_path : audioSources.front();
12548 cmd <<
" -i \"" << audioSource <<
"\"";
12550 cmd <<
" -map 0:v:0? -map 1:a:0?"
12551 <<
" -c:v copy -c:a aac -b:a 192k";
12552 if (mux_duration > 0.0) {
12553 cmd <<
" -t " << std::fixed << std::setprecision(6) << mux_duration;
12555 cmd <<
" -shortest";
12557 cmd <<
" -movflags +faststart";
12559 cmd <<
" \"" << tmp_out <<
"\" 2>&1";
12560 mx::system_out <<
"acmx2: muxing audio file into video" << (
audio_repeat_mode ?
" (repeating)" :
"") <<
" (shortest stream" << (mux_duration > 0.0 ?
", max " + std::to_string(mux_duration) +
"s" : std::string()) <<
")...\n";
12562 int ret = std::system(cmd.str().c_str());
12563 if (!concatPath.empty()) {
12564 std::error_code removeError;
12565 std::filesystem::remove(concatPath, removeError);
12569 std::rename(tmp_out.c_str(),
ofilename.c_str());
12572 mx::system_err <<
"acmx2: ffmpeg audio file mux failed (exit code " << ret <<
")\n";
12573 std::remove(tmp_out.c_str());
12599#ifdef AUDIO_ENABLED
12612#ifdef AUDIO_ENABLED
12616 const bool shouldRecordedMux =
false;
12617 const bool shouldFileAudioMux =
false;
12626 int64_t fc =
writer.get_frame_count();
12627 double ts = (
fps > 0.0) ?
static_cast<double>(fc) /
fps : 0.0;
12628 mx::system_out <<
"acmx2: wrote " << fc <<
" frames (" <<
static_cast<int>(ts / 3600) <<
":" <<
static_cast<int>(ts / 60) % 60 <<
":" <<
static_cast<int>(ts) % 60 <<
") to file: " <<
ofilename <<
"\n";
12634 if (shouldTransferAudio) {
12636 mx::system_out <<
"acmx2: copied audio track from: " <<
filename <<
" to " <<
ofilename <<
"\n";
12638 if (shouldRecordedMux)
12640 if (shouldFileAudioMux)
12657 bool recording =
writer.is_open();
12659#ifdef AUDIO_ENABLED
12670 int64_t final_frame_count =
writer.get_frame_count();
12671 double total_secs = (
fps > 0.0) ?
static_cast<double>(final_frame_count) /
fps : 0.0;
12672 uint64_t hours = 0, minutes = 0, seconds = 0;
12673 hours =
static_cast<uint64_t
>(total_secs / 3600);
12674 minutes =
static_cast<uint64_t
>(total_secs / 60) % 60;
12675 seconds =
static_cast<uint64_t
>(total_secs) % 60;
12676 std::ostringstream timerStr;
12677 timerStr << std::setfill(
'0') << std::setw(2) << hours <<
":" << std::setfill(
'0') << std::setw(2) << minutes <<
":" << std::setfill(
'0') << std::setw(2) << seconds;
12679 mx::system_out <<
"acmx2: " <<
" wrote " << timerStr.str() <<
" (" << final_frame_count <<
" frames) to file: " <<
ofilename <<
"\n";
12682 mx::system_out <<
"acmx2: copied audio track from: " <<
filename <<
" to " <<
ofilename <<
"\n";
12708 if (
event->type == SDL_QUIT || (
event->type == SDL_KEYDOWN &&
event->key.keysym.sym == SDLK_ESCAPE)) {
12709 auto *win =
static_cast<MainWindow *
>(userdata);
12710 auto *view =
static_cast<ACView *
>(win->object.get());
12711 if (view && view->needsAsyncShutdown()) {
12712 view->requestStop();
12721 util.path = args.
path;
12724 SDL_Surface *ico = png::LoadPNG(util.getFilePath(
"data/win-icon.png").c_str());
12726 throw mx::Exception(
"Could not load icon: " + util.getFilePath(
"data/win-icon.png"));
12728 setWindowIcon(ico);
12729 SDL_FreeSurface(ico);
12732 setObject(
new ACView(args));
12733 object->load(
this);
12762 MainWindow(
const MXArguments &args,
const OpenGLContextConfig &context_config,
bool headless) : gl::GLWindow(
"ACMX2", args.tw, args.th, false, gl::GLMode::DESKTOP, context_config.major, context_config.minor, false),
silent_mode(true) {
12763 static_cast<void>(headless);
12764 SDL_HideWindow(getWindow());
12778 glClearColor(0.f, 0.f, 0.0f, 1.0f);
12779 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
12780 glViewport(0, 0, w, h);
12781#if defined(__linux__) || defined(__APPLE__) || defined(_WIN32)
12782 if (
silent_mode && g_shutdown_requested.load(std::memory_order_relaxed)) {
12783 auto *view =
static_cast<ACView *
>(
object.get());
12785 static std::atomic<bool> reported{
false};
12786 if (!reported.exchange(
true)) {
12787 mx::system_out <<
"\nacmx2: Ctrl+C received - finishing current frame and "
12788 "flushing output file...\n";
12789 mx::system_out.flush();
12790 view->requestStopNoMux();
12796 object->draw(
this);
12812#ifdef ACMX2_WITH_CUDA
12813 int device_count = 0;
12814 cudaError_t error = cudaGetDeviceCount(&device_count);
12815 if (error != cudaSuccess || device_count == 0) {
12816 std::cerr <<
"OpenCV Cuda Support not found." << std::endl;
12817 std::cerr <<
"Reason: " << cudaGetErrorString(error) << std::endl;
12818 std::cerr <<
"Check: Are NVIDIA drivers installed? Is the GPU seated?" << std::endl;
12819 exit(EXIT_FAILURE);
12821 std::cout <<
"🚀 GPU Acceleration Active: " << device_count <<
" device(s) found." << std::endl;
12823 for (
int i = 0; i < device_count; ++i) {
12825 cv::cuda::printShortCudaDeviceInfo(i);
12828 cv::cuda::printShortCudaDeviceInfo(cv::cuda::getDevice());
12832 static_cast<void>(list_only);
12833 std::cerr <<
"acmx2: CUDA support was disabled at build time (ACMX2_WITH_CUDA not defined).\n";
12856 const char *no_color = std::getenv(
"NO_COLOR");
12857 if (no_color && no_color[0] !=
'\0') {
12861 const char *force = std::getenv(
"CLICOLOR_FORCE");
12862 if (force && force[0] !=
'0' && force[0] !=
'\0') {
12866#if defined(__linux__) || defined(__APPLE__)
12867 if (!isatty(fileno(stdout))) {
12874 const char *term = std::getenv(
"TERM");
12875 if (!term || std::strcmp(term,
"dumb") == 0) {
12885 c.
reset =
"\033[0m";
12886 c.
title =
"\033[1;96m";
12888 c.
flag =
"\033[1;92m";
12889 c.
arg =
"\033[36m";
12890 c.
desc =
"\033[0;97m";
12896 template <
typename Stream>
void printSection(Stream &out,
const CliColors &c, std::string_view name,
const std::vector<HelpEntry> &entries) {
12898 for (
const auto &entry : entries) {
12899 out <<
" " << c.
flag << entry.flags << c.
reset <<
"\n";
12900 out <<
" " << c.
desc << entry.description << c.
reset <<
"\n";
12901 if (!entry.example.empty()) {
12902 out <<
" " << c.
example <<
"example: " << entry.example << c.
reset <<
"\n";
12909 out << c.
title <<
"\nArguments" << c.
reset <<
"\n";
12910 out << c.
example <<
"Short and long forms are equivalent; values shown in <> are required." << c.
reset <<
"\n";
12912 printSection(out, c,
"General", {{
"-v, -h, --help, --version",
"Show this information screen and keyboard controls.",
"acmx2 --help"}, {
"-p <path>, --path <path>",
"Set assets root directory (shaders, data files, defaults).",
"acmx2 --path ./data"}, {
"-r <WxH>, --resolution <WxH>",
"Set output/window resolution (for display and recording).",
"acmx2 --resolution 1920x1080"}, {
"-N, --fullscreen",
"Start in fullscreen mode (Escape to exit fullscreen).",
"acmx2 --fullscreen"}, {
"--silent",
"Run video or graphics-file rendering headlessly (no preview window).",
"acmx2 -g image.png -o out.mp4 --duration 10 --silent"}, {
"--duration <seconds>",
"Auto-stop recording/output after elapsed seconds.",
"acmx2 -i in.mp4 -o out.mp4 --duration 30"}, {
"--max-size <MB>",
"Auto-stop when output file size exceeds MB.",
"acmx2 -i in.mp4 -o out.mp4 --max-size 500.0"}});
12914 printSection(out, c,
"Input Source", {{
"-i <file>, --input <file>",
"Input video file.",
"acmx2 --input clip.mp4"}, {
"-g <file>, --graphic <file>",
"Input still image instead of camera/video.",
"acmx2 --graphic frame.png"}, {
"-d <idx>, --device <idx>",
"Camera device index to open.",
"acmx2 --device 0"}, {
"-c <WxH>, --camera-res <WxH>",
"Request camera capture resolution.",
"acmx2 --camera-res 1280x720"}, {
"--enumerate-device <idx>",
"Print camera resolutions/formats supported by device and exit.",
"acmx2 --enumerate-device 0"}, {
"--use-yuv",
"Prefer YUYV camera capture over MJPG for compatible devices.",
"acmx2 --device 0 --use-yuv"}});
12918 "Shaders And Visual Pipeline",
12919 {{
"-s <library-dir>, --shaders <library-dir>",
"Use a shader library directory (library.json preferred, index.txt fallback).",
"acmx2 --shaders ./shaders"},
12920 {
"-f <frag.glsl>, --fragment <frag.glsl>",
"Use a single fragment shader file directly.",
"acmx2 --fragment ./shaders/wave.glsl"},
12921 {
"--shader <index>",
"Select initial shader index from the active library.",
"acmx2 --shaders ./shaders --shader 3"},
12922 {
"--shader-pass <list>",
"Run multiple shader indices per frame (comma-separated).",
"acmx2 --shader-pass 0,4,7"},
12923 {
"--playlist <file>",
"Load shader playlist text file (one shader name per line).",
"acmx2 --playlist live_set.txt"},
12924 {
"--cross-fade <seconds>",
"Set smooth transition time between playlist shader switches.",
"acmx2 --playlist live_set.txt --cross-fade 1.25"},
12925 {
"--autopilot-frames <N>",
"Auto-switch to random playlist shader every N rendered frames (minimum 4).",
"acmx2 --shaders ./shaders --autopilot-frames 240"},
12926 {
"--autopilot-timeout <N>",
"Alias for --autopilot-frames (minimum 4).",
"acmx2 --shaders ./shaders --autopilot-timeout 240"},
12927 {
"--autopilot-random <N>",
"Use random autopilot interval 4..N frames for each J/Y autoplay switch.",
"acmx2 --shaders ./shaders --autopilot-random 300"},
12928 {
"--time-speed <mult>",
"Scale shader time uniform speed (1.0 = normal).",
"acmx2 --time-speed 0.5"},
12929 {
"--normalized",
"Advance time_f by a fixed output-frame interval instead of wall time.",
"acmx2 --normalized --time-speed 0.5"},
12930 {
"--build <library-path>",
"Compile shader library into cache, then exit.",
"acmx2 --build ./shaders"},
12931 {
"--remove-broken <library-path>",
"Compile-check each shader and remove failing manifest entries, then exit.",
"acmx2 --remove-broken ./shaders"},
12932 {
"--no-cache",
"Disable shader binary cache and always compile at startup.",
"acmx2 --no-cache"},
12933 {
"--texture-cache",
"Enable texture/frame cache for cache-aware shader effects.",
"acmx2 --texture-cache"},
12934 {
"--cache-delay <frames>",
"Delay frame cache feed by N frames for temporal effects.",
"acmx2 --texture-cache --cache-delay 6"},
12935 {
"--texture-cache-size <N>",
"Set texture cache ring buffer size (1-64, default 8).",
"acmx2 --texture-cache --texture-cache-size 16"},
12936 {
"--enable-3d",
"Enable 3D object rendering pipeline.",
"acmx2 --enable-3d"},
12937 {
"--model <file>",
"Load a custom 3D model file for the 3D scene.",
"acmx2 --enable-3d --model scene.obj"},
12938 {
"--flip",
"Flip final output vertically before display/encode.",
"acmx2 --flip"},
12939 {
"--rotate <mode>",
"Rotate input frames clockwise, 180 degrees, or counterclockwise.",
"acmx2 --rotate clockwise"}});
12941 printSection(out, c,
"Texture Array Cache", {{
"--texture-cache-array",
"Store frame history in one sampler2DArray named history.",
"acmx2 --texture-cache-array"}});
12945 "DNN And ONNX Models",
12946 {{
"--human <file>",
"Load ONNX human segmentation model (e.g., pphumanseg .onnx) to isolate foreground person.",
"acmx2 --human human_seg.onnx -i input.mp4 -o output.mp4"},
12947 {
"--background",
"When --human is used, apply shaders only to background; composite person on top.",
"acmx2 --human model.onnx --background"},
12948 {
"--black <threshold>",
"Set mask black point / shadow crush threshold for color/segmentation masks (default: 0.35).",
"acmx2 --human seg.onnx --black 0.25"},
12949 {
"--white <threshold>",
"Set mask white point / opacity saturation threshold for color/segmentation masks (default: 0.75).",
"acmx2 --human seg.onnx --white 0.85"},
12950 {
"--edge <file>",
"Load ONNX edge detection model (e.g., Dexined .onnx) to replace frame with edge map.",
"acmx2 --edge edges.onnx -i video.mp4 -o edges.mp4"},
12951 {
"--onnx <file>",
"Load generic ONNX model from YAML config file; replaces frame with model output.",
"acmx2 --onnx bubble.yaml -i input.mp4 -o output.mp4"},
12952 {
"--check-dnn",
"Report whether this build has OpenCV DNN support enabled.",
"acmx2 --check-dnn"}});
12954 printSection(out, c,
"GPU And CUDA", {{
"--gpu-filter <list>",
"Apply CUDA filter chain by index list (comma-separated).",
"acmx2 --gpu-filter 1,12,18"}, {
"--gpu-buffer <N>",
"Set GPU temporal frame buffer size (4..32).",
"acmx2 --gpu-buffer 12"}, {
"--list-filters",
"List all built-in GPU filters and their indices.",
"acmx2 --list-filters"}, {
"-m <idx>, --cuda-device <idx>",
"Select CUDA device index to run processing on.",
"acmx2 --cuda-device 0"}, {
"--list-cuda-devices",
"List CUDA devices visible to the runtime.",
"acmx2 --list-cuda-devices"}, {
"--check-cuda",
"Report whether this build has CUDA support enabled.",
"acmx2 --check-cuda"}});
12958 "Recording And Encoding",
12959 {{
"-o <file>, --output <file>",
"Write processed video to output file.",
"acmx2 -i in.mp4 -o out.mp4"},
12960 {
"--png",
"Video file mode: write output as PNG frame sequence in an output subdirectory.",
"acmx2 -i in.mp4 -o out.mp4 --png"},
12961 {
"--generate <N>",
"Save a PNG frame every N frames in an output subdirectory (video or camera mode).",
"acmx2 -i in.mp4 --generate 30"},
12962 {
"-e <prefix>, --prefix <prefix>",
"Snapshot filename prefix for captured frames.",
"acmx2 --prefix snap/frame_"},
12963 {
"-u <fps>, --fps <fps>",
"Set output frame rate for recording.",
"acmx2 --fps 60"},
12964 {
"-b <crf>, --bitrate <crf>",
"Legacy CRF quality option for encoder.",
"acmx2 --bitrate 20"},
12965 {
"--encode-preset <name>",
"Encoder speed/quality preset (ultrafast .. veryslow).",
"acmx2 --encode-preset fast"},
12966 {
"--encode-tune <name>",
"Tune encoder for content type or low latency.",
"acmx2 --encode-tune film"},
12967 {
"--encode-crf <0-51>",
"Set encoder quality directly (lower = better quality/larger file).",
"acmx2 --encode-crf 18"},
12968 {
"--encode-codec <name>",
"Select auto/software/nvenc or an exact installed FFmpeg encoder.",
"acmx2 --encode-codec libx265"},
12969 {
"--list-encoders",
"List FFmpeg video encoders visible to MXWrite and exit.",
"acmx2 --list-encoders"},
12970 {
"--list-encoder-options <name>",
"List AVOptions accepted by one exact encoder and exit.",
"acmx2 --list-encoder-options libx265"},
12971 {
"--encode-realtime",
"Enable low-latency encoder settings for live pipelines.",
"acmx2 --encode-realtime"},
12972 {
"--no-drop",
"File/graphics mode: never drop frames; ignored for webcams.",
"acmx2 -i in.mp4 -o out.mp4 --no-drop"},
12973 {
"--display-filter",
"Show current shader/stack and GPU filter in upper-left corner.",
"acmx2 --display-filter"},
12974 {
"--use-watermark <text>",
"Enable watermark with given text in recorded videos (upper-left).",
"acmx2 --use-watermark \"My Channel\""},
12975 {
"--use-watermark-color <r,g,b>",
"Watermark text color as 0-255 components.",
"acmx2 --use-watermark-color 255,255,0"},
12976 {
"--copy-audio",
"Mux input audio track into encoded output when possible.",
"acmx2 -i in.mp4 -o out.mp4 --copy-audio"},
12977 {
"-a, --repeat",
"Loop video input source continuously.",
"acmx2 -i loop.mp4 --repeat"}});
12979 printSection(out, c,
"Advanced Encoder Parameters", {{
"--encode-params <string>",
"Pass additional FFmpeg-style video encoder options through MXWrite.",
"acmx2 --encode-codec hevc_nvenc --encode-params \"-preset p6 -tune lossless -profile:v rext -pix_fmt yuv444p\""}});
12981#ifdef AUDIO_ENABLED
12984 "Audio Reactivity",
12985 {{
"-w, --enable-audio",
"Enable audio-reactive shader modulation.",
"acmx2 --enable-audio"},
12986 {
"-l <N>, --channels <N>",
"Number of audio channels to capture/process.",
"acmx2 --channels 2"},
12987 {
"-q <value>, --sense <value>",
"Set audio sensitivity multiplier for visual response.",
"acmx2 --sense 1.4"},
12988 {
"--audio-warm-rate <value>",
"Startup audio warmup rate in 1/sec (0.5 ~= 2s fade-in, 1.0 ~= 1s, 0 disables warmup).",
"acmx2 --enable-audio --audio-warm-rate 0.35"},
12989 {
"-y, --pass-through",
"Play live input or file audio through the selected output device.",
"acmx2 --audio-file soundtrack.mp3 --pass-through"},
12990 {
"--audio-input <device>",
"Select input audio device name/id.",
"acmx2 --audio-input \"USB Audio\""},
12991 {
"--audio-output <device>",
"Select pass-through output device name/id.",
"acmx2 --audio-output \"Built-in Output\""},
12992 {
"--list-devices",
"List available audio input/output devices.",
"acmx2 --list-devices"},
12993 {
"--record-audio <wav-file>",
"Record captured audio stream to a WAV file.",
"acmx2 --record-audio take.wav"},
12994 {
"--record-gain <0.0-2.0>",
"Set recording gain multiplier (1.0 = unity).",
"acmx2 --record-gain 1.2"},
12995 {
"--audio-file <file>",
"Use an audio file or M3U playlist as reactivity source instead of microphone input.",
"acmx2 --audio-file soundtrack.m3u"},
12996 {
"--audio-trunc",
"Stop playback/output when the audio source reaches EOF.",
"acmx2 --audio-file soundtrack.m3u --audio-trunc"},
12997 {
"--audio-repeat",
"Restart file audio or the full playlist at EOF.",
"acmx2 --audio-file soundtrack.m3u --audio-repeat"},
12998 {
"--enable-audio-buffers <N>",
"Allocate one sampler1DArray with N spectrum-history layers (GPU-limited).",
"acmx2 --enable-audio --enable-audio-buffers 8"},
12999 {
"--check-audio",
"Report whether this build has audio support enabled.",
"acmx2 --check-audio"}});
13003 printSection(out, c,
"MIDI Control", {{
"--midi-map <file>",
"Load MIDI mapping configuration file.",
"acmx2 --midi-map midi.midi_cfg"}, {
"--midi-device <idx>",
"Select MIDI input device index.",
"acmx2 --midi-device 0"}, {
"--list-midi",
"List available MIDI input devices.",
"acmx2 --list-midi"}, {
"--check-midi",
"Report whether this build has MIDI support enabled.",
"acmx2 --check-midi"}});
13006 printSection(out, c,
"Runtime Overlay", {{
"--disable-counter",
"Hide timer and FPS overlay text.",
"acmx2 --disable-counter"}});
13011 out << c.
title <<
"\nKeyboard Controls" << c.
reset <<
"\n";
13016 {{
"Escape",
"Quit.",
""},
13017 {
"Ctrl+X",
"Quit without audio mux.",
""},
13018 {
"Up Arrow",
"Crossfade to the previous shader (or playlist entry in playlist/autopilot mode).",
""},
13019 {
"Down Arrow",
"Crossfade to the next shader (or playlist entry in playlist/autopilot mode).",
""},
13020 {
"Shift+Up Arrow",
"In playlist/autopilot mode: change post-multipass shader backward.",
""},
13021 {
"Shift+Down Arrow",
"In playlist/autopilot mode: change post-multipass shader forward.",
""},
13022 {
"Left Arrow",
"Previous GPU filter (if enabled).",
""},
13023 {
"Right Arrow",
"Next GPU filter (if enabled).",
""},
13024 {
"Space",
"Enable/disable processing.",
""},
13025 {
"L",
"Toggle video freeze (Video/Image modes).",
""},
13026 {
"P",
"Toggle pause (Video/Image) or toggle shader playlist.",
""},
13027 {
"J",
"Toggle autopilot mode (requires playlist).",
""},
13028 {
"Y",
"Toggle sequential autopilot (cycles playlist in order, requires playlist).",
""},
13029 {
"N",
"Toggle random crossfade selection for autopilot shader switches.",
""},
13030 {
"T",
"Enable/disable time.",
""},
13031 {
"U / I",
"Step time when time is disabled.",
""},
13032 {
"Page Up / Page Down",
"Increase/decrease time speed.",
""},
13033 {
"M",
"Toggle multi-pass / multi-shader pass.",
""},
13034 {
"F",
"Toggle fullscreen.",
""},
13035 {
"Q",
"Toggle reactive time (if AUDIO_ENABLED).",
""},
13036 {
"Insert",
"Increase audio sensitivity.",
""},
13037 {
"Delete",
"Decrease audio sensitivity.",
""},
13038 {
"End",
"Toggle spectrum sensitivity scaling.",
""},
13039 {
"Home",
"Toggle audio delta time scaling.",
""},
13040 {
"3",
"Toggle 2D/3D mode.",
""}});
13042 printSection(out, c,
"Snapshots", {{
"Z",
"Save PNG snapshot (SDR 8-bit; HDR mode still outputs SDR PNG).",
""}, {
"4",
"Save TIFF snapshot (SDR: 8-bit RGBA; HDR: 16-bit RGBA; requires ACMX2_WITH_TIFF).",
""}, {
"5",
"Save lossless WebP snapshot (HDR is tone-mapped; requires ACMX2_WITH_WEBP).",
""}, {
"6",
"Save raw RGBA snapshot (HDR: 16-bit RGBA, otherwise 8-bit RGBA).",
"ffplay -f rawvideo -pixel_format rgba64le -video_size WxH file.raw"}});
13044 printSection(out, c,
"3D Mode", {{
"W / A / S / D",
"Look around.",
""}, {
"V",
"Toggle view rotation.",
""}, {
"O",
"Toggle oscillation.",
""}, {
"X",
"Reset camera distance.",
""}, {
"+ / -",
"Increase/decrease camera distance.",
""}, {
"B",
"Increase movement speed.",
""}, {
"N (held in 3D)",
"Decrease movement speed.",
""}, {
"C",
"Toggle object wave.",
""}, {
"E",
"Enable/disable watermark.",
""}, {
"]",
"Increase model scale.",
""}, {
"[",
"Decrease model scale.",
""}, {
". (period)",
"Increase camera rotation speed.",
""}, {
", (comma)",
"Decrease camera rotation speed.",
""}});
13045 printSection(out, c,
"Environment Variables", {{
"ACMX2_PATH",
"Default assets root directory (equivalent to --path). Used when --path is not specified.",
"export ACMX2_PATH=/path/to/share/acmx2"}, {
"ACMX2_SHADER_PATH",
"Default shader library index file or directory (equivalent to --shaders). Used when neither --shaders nor --fragment is specified.",
"export ACMX2_SHADER_PATH=/path/to/shaders"}});
13053 mx::system_out <<
"https://lostsidedead.biz\n";
13058 if (include_branding)
13076 Argz<std::string> parser(argc, argv);
13077 parser.addOptionSingle(
'v',
"Display help message")
13078 .addOptionSingle(
'h',
"Display help message")
13079 .addOptionSingleValue(
'p',
"assets path")
13080 .addOptionDoubleValue(
'P',
"path",
"assets path")
13081 .addOptionSingleValue(
'r',
"Resolution WidthxHeight")
13082 .addOptionDoubleValue(
'R',
"resolution",
"Resolution WidthxHeight")
13083 .addOptionSingleValue(
'd',
"Camera Device")
13084 .addOptionDoubleValue(
'D',
"device",
"Device Index")
13085 .addOptionSingleValue(
'c',
"Camera Resolution")
13086 .addOptionDoubleValue(
'C',
"camera-res",
"Camera Resolution")
13087 .addOptionSingleValue(
'i',
"Input file")
13088 .addOptionSingleValue(
'g',
"Input Image")
13089 .addOptionDoubleValue(
'G',
"graphic",
"Input graphics file")
13090 .addOptionDoubleValue(
'I',
"input",
"Input file")
13091 .addOptionSingleValue(
's',
"Shader Library Index File")
13092 .addOptionDoubleValue(
'S',
"shaders",
"Shader Library Index File")
13093 .addOptionSingleValue(
'f',
"Fragment Shader")
13094 .addOptionDoubleValue(
'F',
"fragment",
"Fragment Shader")
13095 .addOptionDoubleValue(
'H',
"shader",
"Shader Index")
13096 .addOptionDoubleValue(622,
"shader-file",
"Shader filename in the active library")
13097 .addOptionSingleValue(
'e',
"Save Prefix")
13098 .addOptionDoubleValue(
'E',
"prefix",
"Save Prefix")
13099 .addOptionSingleValue(
'o',
"output file")
13100 .addOptionDoubleValue(
'O',
"output",
"output file")
13101 .addOptionSingleValue(
'b',
"Bitrate in CRF")
13102 .addOptionDoubleValue(
'B',
"bitrate",
"Bitrate in CRF")
13103 .addOptionSingleValue(
'u',
"frames per second")
13104 .addOptionDoubleValue(
'U',
"fps",
"Frames per second")
13105 .addOptionSingle(
'a',
"Repeat")
13106 .addOptionDouble(
'A',
"repeat",
"Video repeat")
13107 .addOptionSingle(
'n',
"fullscreen")
13108 .addOptionDouble(256,
"texture-cache",
"Enable texture cache")
13109 .addOptionDoubleValue(257,
"cache-delay",
"Cache delay in frames")
13110 .addOptionDoubleValue(275,
"texture-cache-size",
"Ring buffer size for texture cache (default 8)")
13111 .addOptionDouble(276,
"texture-cache-array",
"Expose texture cache as sampler2DArray history")
13112 .addOptionDouble(258,
"copy-audio",
"Copy audio track")
13113 .addOptionDouble(259,
"enable-3d",
"Enable 3D cube")
13114 .addOptionDoubleValue(260,
"model",
"Model file")
13115 .addOptionDoubleValue(700,
"human",
"Human segmentation model (PPHS .onnx) -- isolate person via DNN")
13116 .addOptionDouble(701,
"background",
"With --human: apply shaders only to the background; composite person on top")
13117 .addOptionDoubleValue(702,
"black",
"Mask black point / shadow crush threshold (default 0.35)")
13118 .addOptionDoubleValue(703,
"white",
"Mask white point / opacity saturation threshold (default 0.75)")
13119 .addOptionDoubleValue(704,
"edge",
"Edge detection model (Dexined .onnx) -- replace frame with edge map")
13120 .addOptionDoubleValue(705,
"onnx",
"Generic ONNX model YAML config -- replace frame with DNN output")
13121 .addOptionDouble(621,
"check-dnn",
"Report whether OpenCV DNN support is compiled in")
13122 .addOptionDouble(261,
"help",
"print help info")
13123 .addOptionDouble(262,
"version",
"print version and help info")
13124 .addOptionDoubleValue(400,
"gpu-filter",
"GPU filter indices (comma-separated)")
13125 .addOptionDoubleValue(401,
"gpu-buffer",
"GPU frame buffer size (4-32)")
13126 .addOptionDouble(402,
"list-filters",
"List available GPU filters")
13127 .addOptionDouble(403,
"disable-counter",
"Disable timer and FPS counter overlay")
13128 .addOptionSingleValue(
'm',
"CUDA device index")
13129 .addOptionDoubleValue(
'M',
"cuda-device",
"CUDA device index")
13130 .addOptionDouble(404,
"list-cuda-devices",
"List available CUDA devices")
13131 .addOptionDouble(415,
"check-cuda",
"Report whether CUDA support is compiled in")
13132#ifdef AUDIO_ENABLED
13133 .addOptionSingle(
'w',
"Enable Audio Reactivity")
13134 .addOptionDouble(
'W',
"enable-audio",
"enabled audio reacitivty")
13135 .addOptionSingleValue(
'l',
"Audio channels")
13136 .addOptionDoubleValue(
'L',
"channels",
"Audio channels")
13137 .addOptionSingleValue(
'q',
"Audio Sensitivty")
13138 .addOptionDoubleValue(
'Q',
"sense",
"Audio sensitivty")
13139 .addOptionSingle(
'y',
"Enable Audio Pass-through")
13140 .addOptionDouble(
'Y',
"pass-through",
"Enable audio pass through")
13141 .addOptionDoubleValue(300,
"audio-input",
"Audio input device")
13142 .addOptionDoubleValue(301,
"audio-output",
"Audio output device")
13143 .addOptionDouble(302,
"list-devices",
"list audio devices")
13144 .addOptionDoubleValue(303,
"record-audio",
"Record captured audio to WAV file")
13145 .addOptionDoubleValue(304,
"record-gain",
"Recording volume gain 0.0-2.0 (default: 1.0)")
13146 .addOptionDoubleValue(305,
"audio-file",
"Use an audio file or M3U playlist for reactivity instead of mic")
13147 .addOptionDouble(306,
"audio-trunc",
"Stop playback when the audio file reaches the end")
13148 .addOptionDoubleValue(307,
"enable-audio-buffers",
"Allocate a spectrum-history sampler1DArray with N layers")
13149 .addOptionDoubleValue(308,
"audio-warm-rate",
"Startup audio warmup rate (1/sec, default: 0.5)")
13150 .addOptionDouble(309,
"audio-repeat",
"Restart audio file playback at the end")
13152 .addOptionDouble(
'N',
"fullscreen",
"Fullscreen Window (Escape to quit)")
13153 .addOptionDouble(405,
"silent",
"Silent mode - process video without window, (video files only)")
13154 .addOptionDoubleValue(406,
"shader-pass",
"Shader pass indices (comma-separated, e.g. 0,1,2)")
13155 .addOptionDoubleValue(623,
"shader-pass-files",
"Shader pass filenames (length-prefixed)")
13156 .addOptionDoubleValue(407,
"build",
"Build shader cache for specified library path (compiles shaders and exits)")
13157 .addOptionDouble(408,
"no-cache",
"Disable shader caching (always recompile shaders)")
13158 .addOptionDoubleValue(416,
"remove-broken",
"Compile each shader in library path; remove failures from its manifest, then exit")
13159 .addOptionDoubleValue(409,
"time-speed",
"Constant time_f speed multiplier (default: 1.0)")
13160 .addOptionDouble(620,
"normalized",
"Use deterministic output-frame time_f advancement")
13161 .addOptionDoubleValue(410,
"playlist",
"Shader playlist text file (one shader name per line, P to toggle)")
13162 .addOptionDoubleValue(417,
"autopilot-frames",
"Autopilot frame interval; switch to a random playlist shader every N frames (minimum 4, J toggles)")
13163 .addOptionDoubleValue(420,
"autopilot-timeout",
"Alias for --autopilot-frames")
13164 .addOptionDoubleValue(418,
"autopilot-random",
"Autopilot random interval upper bound; each J/Y switch picks 4..N frames")
13165 .addOptionDoubleValue(419,
"autiopilot-random",
"Alias for --autopilot-random")
13166 .addOptionDoubleValue(411,
"duration",
"Recording duration in seconds (float); stop recording and exit after elapsed")
13167 .addOptionDoubleValue(610,
"max-size",
"Stop recording when output file exceeds size in MB (float)")
13168 .addOptionDouble(611,
"png",
"Video file mode: write PNG frames to output subdirectory instead of encoding video")
13169 .addOptionDoubleValue(612,
"generate",
"Save a PNG frame every N frames to an output subdirectory (video or camera mode)")
13170 .addOptionDouble(613,
"interface-shm",
"Enable Qt interface shared-memory control channel")
13171 .addOptionDoubleValue(412,
"cross-fade",
"Crossfade duration in seconds when switching playlist shaders (default: 0.5)")
13172 .addOptionDoubleValue(413,
"enumerate-device",
"List supported resolutions for a camera device index")
13173 .addOptionDouble(414,
"use-yuv",
"Use YUV (YUYV) camera format instead of MJPG")
13174 .addOptionDoubleValue(600,
"encode-preset",
"Encoder preset: ultrafast..veryslow or NVENC p1..p7 (default: medium)")
13175 .addOptionDoubleValue(601,
"encode-tune",
"Encoder tune: software tunes or NVENC hq,uhq,ll,ull,lossless (default: none)")
13176 .addOptionDoubleValue(602,
"encode-crf",
"Encoder CRF quality 0 (best) .. 51 (worst), default 18")
13177 .addOptionDoubleValue(603,
"encode-codec",
"Encoder policy or exact FFmpeg encoder name (default: auto)")
13178 .addOptionDouble(604,
"encode-realtime",
"Enable low-latency realtime encoding flags")
13179 .addOptionDouble(605,
"flip",
"Vertical flip output frames")
13180 .addOptionDoubleValue(617,
"rotate",
"Rotate input frames: clockwise, 180, or counterclockwise")
13181 .addOptionDouble(606,
"no-drop",
"File/graphics mode: never drop frames; ignored for webcams")
13182 .addOptionDouble(607,
"display-filter",
"Display current shader/stack and GPU filter in upper-left corner")
13183 .addOptionDoubleValue(608,
"use-watermark",
"Enable watermark with the given text in upper-left corner of recorded video")
13184 .addOptionDoubleValue(609,
"use-watermark-color",
"Watermark color as r,g,b each 0-255 (default: 255,0,150)")
13185 .addOptionDoubleValue(614,
"encode-params",
"Additional FFmpeg-style video encoder parameters passed through MXWrite")
13186 .addOptionDouble(615,
"list-encoders",
"List FFmpeg video encoders available to MXWrite")
13187 .addOptionDoubleValue(616,
"list-encoder-options",
"List options for an exact FFmpeg video encoder")
13189 .addOptionDoubleValue(500,
"midi-map",
"MIDI config file (.midi_cfg)")
13190 .addOptionDoubleValue(501,
"midi-device",
"MIDI input device index")
13191 .addOptionDouble(502,
"list-midi",
"List available MIDI input devices")
13197 exit(EXIT_SUCCESS);
13200 for (
int i = 1; i < argc; ++i) {
13201 if (std::string(argv[i]) ==
"--check-cuda") {
13202#ifdef ACMX2_WITH_CUDA
13203 std::cout <<
"CUDA: enabled" << std::endl;
13205 std::cout <<
"CUDA: disabled" << std::endl;
13207 return EXIT_SUCCESS;
13209 if (std::string(argv[i]) ==
"--check-midi") {
13211 std::cout <<
"MIDI: enabled" << std::endl;
13213 std::cout <<
"MIDI: disabled" << std::endl;
13215 return EXIT_SUCCESS;
13217 if (std::string(argv[i]) ==
"--check-audio") {
13218#ifdef AUDIO_ENABLED
13219 std::cout <<
"AUDIO: enabled" << std::endl;
13221 std::cout <<
"AUDIO: disabled" << std::endl;
13223 return EXIT_SUCCESS;
13225 if (std::string(argv[i]) ==
"--check-dnn") {
13226#ifdef ACMX2_WITH_DNN
13227 std::cout <<
"OpenCV DNN: enabled" << std::endl;
13229 std::cout <<
"OpenCV DNN: disabled" << std::endl;
13231 return EXIT_SUCCESS;
13237 Argument<std::string> arg;
13241 while ((value = parser.proc(arg)) != -1) {
13248 exit(EXIT_SUCCESS);
13252 args.
path = arg.arg_value;
13256 auto pos = arg.arg_value.find(
"x");
13257 if (pos == std::string::npos) {
13258 mx::system_err <<
"Error invalid resolution use WidthxHeight\n";
13259 mx::system_err.flush();
13260 exit(EXIT_FAILURE);
13262 std::string left, right;
13263 left = arg.arg_value.substr(0, pos);
13264 right = arg.arg_value.substr(pos + 1);
13265 args.
tw = atoi(left.c_str());
13266 args.
th = atoi(right.c_str());
13267 args.
sizev = cv::Size(args.
tw, args.
th);
13275 auto pos = arg.arg_value.find(
"x");
13276 if (pos == std::string::npos) {
13277 mx::system_err <<
"Error invalid camera resolution use WidthxHeight\n";
13278 mx::system_err.flush();
13279 exit(EXIT_FAILURE);
13281 std::string left, right;
13282 left = arg.arg_value.substr(0, pos);
13283 right = arg.arg_value.substr(pos + 1);
13284 int xw = atoi(left.c_str());
13285 int xh = atoi(right.c_str());
13286 args.
csize = cv::Size(xw, xh);
13295 args.
library = arg.arg_value;
13322 args.
crf = arg.arg_value;
13324 int v = std::stoi(arg.arg_value);
13335 args.
fps_value = atof(arg.arg_value.c_str());
13347 mx::system_out <<
"acmx2: Texture cache enabled.\n";
13351 mx::system_out <<
"acmx2: Cache delay set to: " << args.
cache_delay <<
"\n";
13354 int sz = atoi(arg.arg_value.c_str());
13355 if (sz >= 1 && sz <= 64)
13357 mx::system_out <<
"acmx2: Texture cache size set to: " << args.
cache_size <<
"\n";
13363 mx::system_out <<
"acmx2: Texture cache array enabled as uniform history.\n";
13370 mx::system_out <<
"acmx2: 3D cube enabled.\n";
13382 args.
human_black =
static_cast<float>(std::stod(arg.arg_value));
13385 args.
human_white =
static_cast<float>(std::stod(arg.arg_value));
13395 std::string list = arg.arg_value;
13398 size_t pos = list.find(
',', start);
13399 std::string tok = (pos == std::string::npos) ? list.substr(start) : list.substr(start, pos - start);
13400 if (!tok.empty()) {
13401 int idx = std::stoi(tok);
13405 mx::system_err <<
"acmx2: Invalid GPU filter index: " << idx <<
" (max: " <<
ac_gpu::AC_FILTER_MAX - 1 <<
")\n";
13408 if (pos == std::string::npos)
13426 exit(EXIT_SUCCESS);
13437 exit(EXIT_SUCCESS);
13440#ifdef ACMX2_WITH_CUDA
13441 std::cout <<
"CUDA: enabled\n";
13443 std::cout <<
"CUDA: disabled\n";
13445 exit(EXIT_SUCCESS);
13448#ifdef ACMX2_WITH_DNN
13449 std::cout <<
"OpenCV DNN: enabled\n";
13451 std::cout <<
"OpenCV DNN: disabled\n";
13453 exit(EXIT_SUCCESS);
13455#ifdef AUDIO_ENABLED
13473 if (arg.arg_value ==
"default")
13479 if (arg.arg_value ==
"default")
13486 exit(EXIT_SUCCESS);
13492 args.
record_gain =
static_cast<float>(atof(arg.arg_value.c_str()));
13502 int n = atoi(arg.arg_value.c_str());
13508 float rate =
static_cast<float>(atof(arg.arg_value.c_str()));
13509 if (!std::isfinite(rate) || rate < 0.0f) {
13510 mx::system_err <<
"acmx2: --audio-warm-rate must be >= 0.0\n";
13511 mx::system_err.flush();
13512 exit(EXIT_FAILURE);
13524 std::string pass_list = arg.arg_value;
13527 size_t pos = pass_list.find(
',', start);
13528 std::string tok = (pos == std::string::npos) ? pass_list.substr(start) : pass_list.substr(start, pos - start);
13529 if (!tok.empty()) {
13531 int idx = std::stoi(tok);
13536 mx::system_err <<
"acmx2: Warning: Invalid shader pass index: " << tok <<
"\n";
13539 if (pos == std::string::npos)
13545 mx::system_out <<
"acmx2: Shader pass list enabled with " << args.
shader_pass_list.size() <<
" passes\n";
13550 const std::string passFiles = arg.arg_value;
13552 while (start < passFiles.size()) {
13553 const size_t separator = passFiles.find(
':', start);
13554 if (separator == std::string::npos)
13556 size_t consumed = 0;
13557 size_t nameLength = 0;
13559 nameLength = std::stoull(passFiles.substr(start, separator - start), &consumed);
13563 if (consumed != separator - start || nameLength > passFiles.size() - separator - 1)
13565 const size_t nameStart = separator + 1;
13567 start = nameStart + nameLength;
13569 if (start != passFiles.size()) {
13570 mx::system_err <<
"acmx2: Invalid --shader-pass-files payload\n";
13571 return EXIT_FAILURE;
13585 mx::system_out <<
"acmx2: Shader caching disabled\n";
13588 args.
time_speed =
static_cast<float>(atof(arg.arg_value.c_str()));
13589 mx::system_out <<
"acmx2: Time speed set to: " << args.
time_speed <<
"\n";
13593 mx::system_out <<
"acmx2: Normalized time enabled\n";
13597 mx::system_out <<
"acmx2: Playlist file: " << args.
playlist_file <<
"\n";
13604 mx::system_out <<
"acmx2: Autopilot frames: " << args.
autopilot_frames <<
"\n";
13612 mx::system_out <<
"acmx2: Autopilot random interval enabled (4-" << args.
autopilot_random_timeout <<
" frames)\n";
13615 args.
duration = atof(arg.arg_value.c_str());
13617 mx::system_out <<
"acmx2: Duration set to: " << args.
duration <<
" seconds\n";
13623 mx::system_out <<
"acmx2: Max output size set to: " << std::fixed << std::setprecision(2) << args.
max_size_mb <<
" MB\n";
13630 mx::system_out <<
"acmx2: --png enabled (video-file mode only; ignored for camera mode)\n";
13633 int n = atoi(arg.arg_value.c_str());
13635 mx::system_err <<
"acmx2: --generate requires a positive integer frame interval\n";
13636 exit(EXIT_FAILURE);
13639 mx::system_out <<
"acmx2: --generate " << n <<
": will save a PNG frame every " << n <<
" frames\n";
13644 mx::system_out <<
"acmx2: Qt shared-memory control channel enabled\n";
13648 mx::system_out <<
"acmx2: Crossfade duration set to: " << args.
cross_fade_duration <<
" seconds\n";
13652 int dev_idx = atoi(arg.arg_value.c_str());
13653 std::string dev_path =
"/dev/video" + std::to_string(dev_idx);
13654 int fd = open(dev_path.c_str(), O_RDWR);
13656 mx::system_err <<
"acmx2: Cannot open " << dev_path <<
": " << strerror(errno) <<
"\n";
13657 exit(EXIT_FAILURE);
13659 v4l2_capability cap{};
13660 bool loopback_device =
false;
13661 if (ioctl(fd, VIDIOC_QUERYCAP, &cap) == 0) {
13662 mx::system_out <<
"Device " << dev_idx <<
": " << dev_path <<
"\n";
13663 mx::system_out <<
" Driver : " << cap.driver <<
"\n";
13664 mx::system_out <<
" Card : " << cap.card <<
"\n";
13665 mx::system_out <<
" Bus : " << cap.bus_info <<
"\n";
13666 const std::string driver(
reinterpret_cast<const char *
>(cap.driver));
13667 loopback_device = driver.find(
"v4l2loopback") != std::string::npos || driver.find(
"v4l2 loopback") != std::string::npos;
13670 double current_fps = 0.0;
13671 v4l2_streamparm stream_parameters{};
13672 stream_parameters.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
13673 if (ioctl(fd, VIDIOC_G_PARM, &stream_parameters) == 0 && stream_parameters.parm.capture.timeperframe.numerator != 0) {
13674 const v4l2_fract &interval = stream_parameters.parm.capture.timeperframe;
13675 current_fps =
static_cast<double>(interval.denominator) / interval.numerator;
13677 v4l2_fmtdesc fmt{};
13678 fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
13680 while (ioctl(fd, VIDIOC_ENUM_FMT, &fmt) == 0) {
13681 char fourcc[5] = {
static_cast<char>(fmt.pixelformat & 0xFF),
static_cast<char>((fmt.pixelformat >> 8) & 0xFF),
static_cast<char>((fmt.pixelformat >> 16) & 0xFF),
static_cast<char>((fmt.pixelformat >> 24) & 0xFF),
'\0'};
13682 mx::system_out <<
"\n Format: " << fourcc <<
" (" << fmt.description <<
")\n";
13683 v4l2_frmsizeenum fsize{};
13684 fsize.pixel_format = fmt.pixelformat;
13686 while (ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &fsize) == 0) {
13687 if (fsize.type == V4L2_FRMSIZE_TYPE_DISCRETE) {
13688 mx::system_out <<
" " << fsize.discrete.width <<
"x" << fsize.discrete.height;
13689 std::vector<double> frame_rates;
13690 auto append_frame_rate = [&frame_rates](
double frame_rate) {
13691 if (frame_rate <= 0.0) {
13694 const auto existing = std::find_if(frame_rates.begin(), frame_rates.end(), [frame_rate](
double value) { return std::abs(value - frame_rate) < 0.05; });
13695 if (existing == frame_rates.end()) {
13696 frame_rates.push_back(frame_rate);
13699 v4l2_frmivalenum fival{};
13700 fival.pixel_format = fmt.pixelformat;
13701 fival.width = fsize.discrete.width;
13702 fival.height = fsize.discrete.height;
13704 while (ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &fival) == 0) {
13705 if (fival.type == V4L2_FRMIVAL_TYPE_DISCRETE) {
13706 if (fival.discrete.numerator != 0) {
13707 append_frame_rate(
static_cast<double>(fival.discrete.denominator) / fival.discrete.numerator);
13709 }
else if (fival.type == V4L2_FRMIVAL_TYPE_STEPWISE || fival.type == V4L2_FRMIVAL_TYPE_CONTINUOUS) {
13710 if (fival.stepwise.min.numerator != 0) {
13711 append_frame_rate(
static_cast<double>(fival.stepwise.min.denominator) / fival.stepwise.min.numerator);
13713 if (fival.stepwise.max.numerator != 0) {
13714 append_frame_rate(
static_cast<double>(fival.stepwise.max.denominator) / fival.stepwise.max.numerator);
13720 append_frame_rate(current_fps);
13721 if (loopback_device) {
13726 constexpr double LOOPBACK_FRAME_RATES[] = {24.0, 25.0, 30.0, 50.0, 60.0, 90.0, 120.0, 144.0, 240.0};
13727 for (
double frame_rate : LOOPBACK_FRAME_RATES) {
13728 append_frame_rate(frame_rate);
13732 std::sort(frame_rates.begin(), frame_rates.end(), std::greater<double>());
13734 for (
double frame_rate : frame_rates) {
13735 mx::system_out << (first ?
" @ " :
", ") << std::fixed << std::setprecision(1) << frame_rate <<
" fps";
13738 mx::system_out <<
"\n";
13739 }
else if (fsize.type == V4L2_FRMSIZE_TYPE_STEPWISE || fsize.type == V4L2_FRMSIZE_TYPE_CONTINUOUS) {
13740 mx::system_out <<
" " << fsize.stepwise.min_width <<
"x" << fsize.stepwise.min_height <<
" to " << fsize.stepwise.max_width <<
"x" << fsize.stepwise.max_height <<
" (step " << fsize.stepwise.step_width <<
"x" << fsize.stepwise.step_height <<
")\n";
13749 mx::system_out <<
"acmx2: --enumerate-device is only supported on Linux\n";
13752 exit(EXIT_SUCCESS);
13757 mx::system_out <<
"acmx2: Using YUV (YUYV) camera format\n";
13761 mx::system_out <<
"acmx2: Encoder preset: " << args.
encode_opts.preset <<
"\n";
13764 args.
encode_opts.tune = (arg.arg_value ==
"none") ? std::string{} : arg.arg_value;
13765 mx::system_out <<
"acmx2: Encoder tune: " << (args.
encode_opts.tune.empty() ?
"none" : args.
encode_opts.tune) <<
"\n";
13768 int v = atoi(arg.arg_value.c_str());
13774 args.
crf = std::to_string(v);
13775 mx::system_out <<
"acmx2: Encoder CRF: " << v <<
"\n";
13780 mx::system_out <<
"acmx2: Encoder codec: " << args.
encode_opts.codec <<
"\n";
13784 mx::system_out <<
"acmx2: Encoder realtime mode enabled\n";
13788 mx::system_out <<
"acmx2: Output frame flipping enabled\n";
13791 std::string rotation = arg.arg_value;
13792 std::transform(rotation.begin(), rotation.end(), rotation.begin(), [](
unsigned char character) { return static_cast<char>(std::tolower(character)); });
13793 if (rotation ==
"clockwise" || rotation ==
"cw" || rotation ==
"90" || rotation ==
"90cw") {
13795 mx::system_out <<
"acmx2: Input frame rotation: 90 degrees clockwise\n";
13796 }
else if (rotation ==
"180") {
13798 mx::system_out <<
"acmx2: Input frame rotation: 180 degrees\n";
13799 }
else if (rotation ==
"counterclockwise" || rotation ==
"ccw" || rotation ==
"90ccw" || rotation ==
"270") {
13801 mx::system_out <<
"acmx2: Input frame rotation: 90 degrees counterclockwise\n";
13803 mx::system_err <<
"acmx2: --rotate requires clockwise, 180, or "
13804 "counterclockwise\n";
13805 mx::system_err.flush();
13806 exit(EXIT_FAILURE);
13812 mx::system_out <<
"acmx2: --no-drop enabled (video mode)\n";
13816 mx::system_out <<
"acmx2: --display-filter enabled\n";
13820 mx::system_out <<
"acmx2: --use-watermark text: \"" << args.
watermark_text <<
"\"\n";
13823 const std::string &v = arg.arg_value;
13824 int r = 255, g = 0, b = 150;
13825 size_t c1 = v.find(
',');
13826 size_t c2 = (c1 == std::string::npos) ? std::string::npos : v.find(
',', c1 + 1);
13827 if (c1 != std::string::npos && c2 != std::string::npos) {
13829 r = std::stoi(v.substr(0, c1));
13830 g = std::stoi(v.substr(c1 + 1, c2 - c1 - 1));
13831 b = std::stoi(v.substr(c2 + 1));
13833 mx::system_err <<
"acmx2: --use-watermark-color: invalid value '" << v <<
"'; expected r,g,b\n";
13841 mx::system_err <<
"acmx2: --use-watermark-color: invalid value '" << v <<
"'; expected r,g,b\n";
13847 mx::system_out <<
"acmx2: Extra FFmpeg encoder parameters: " << args.
encode_opts.ffmpeg_options <<
"\n";
13850 auto clean_field = [](std::string value) {
13851 std::replace_if(value.begin(), value.end(), [](
char ch) { return ch ==
'\t' || ch ==
'\r' || ch ==
'\n'; },
' ');
13854 mx::system_out <<
"MXWRITE_ENCODERS\t1\n";
13855 for (
const EncoderInfo &encoder : available_video_encoders()) {
13856 mx::system_out <<
"ENCODER\t" << clean_field(encoder.name) <<
'\t' << clean_field(encoder.long_name) <<
'\t' << clean_field(encoder.codec_name) <<
'\t' << (encoder.hardware ?
"hardware" :
"software") <<
'\t' << (encoder.experimental ?
"experimental" :
"stable") <<
'\t' << clean_field(encoder.pixel_formats) <<
'\n';
13858 mx::system_out.flush();
13859 return EXIT_SUCCESS;
13862 auto clean_field = [](std::string value) {
13863 std::replace_if(value.begin(), value.end(), [](
char ch) { return ch ==
'\t' || ch ==
'\r' || ch ==
'\n'; },
' ');
13866 const std::vector<EncoderOptionInfo> options = video_encoder_options(arg.arg_value);
13867 if (options.empty() && !avcodec_find_encoder_by_name(arg.arg_value.c_str())) {
13868 mx::system_err <<
"acmx2: encoder not found: " << arg.arg_value <<
'\n';
13869 return EXIT_FAILURE;
13871 mx::system_out <<
"MXWRITE_ENCODER_OPTIONS\t1\t" << clean_field(arg.arg_value) <<
'\n';
13872 for (
const EncoderOptionInfo &option : options) {
13873 mx::system_out <<
"OPTION\t" << clean_field(option.name) <<
'\t' << clean_field(option.type) <<
'\t' << clean_field(option.default_value) <<
'\t' << clean_field(option.minimum) <<
'\t' << clean_field(option.maximum) <<
'\t' << clean_field(option.choices) <<
'\t' << clean_field(option.help) <<
'\n';
13875 mx::system_out.flush();
13876 return EXIT_SUCCESS;
13881 mx::system_out <<
"acmx2: MIDI map file: " << args.
midi_map_file <<
"\n";
13885 mx::system_out <<
"acmx2: MIDI device index: " << args.
midi_device <<
"\n";
13890 unsigned int ports = midi.getPortCount();
13892 mx::system_out <<
"No MIDI input devices found.\n";
13894 mx::system_out <<
"MIDI Input Devices (" << ports <<
"):\n";
13895 for (
unsigned int i = 0; i < ports; ++i) {
13896 mx::system_out <<
" " << i <<
": " << midi.getPortName(i) <<
"\n";
13899 }
catch (RtMidiError &e) {
13900 mx::system_err <<
"MIDI error: " << e.getMessage() <<
"\n";
13903 exit(EXIT_SUCCESS);
13909 }
catch (
const ArgException<std::string> &e) {
13910 mx::system_err << e.text() <<
"\n";
13911 mx::system_err.flush();
13912 return EXIT_FAILURE;
13915 if (args.
path.empty()) {
13916 const char *env_path = std::getenv(
"ACMX2_PATH");
13917 if (env_path && env_path[0] !=
'\0') {
13918 args.
path = env_path;
13919 mx::system_out <<
"acmx2: Using ACMX2_PATH environment variable: " << args.
path <<
"\n";
13922 if (!installed_assets.empty()) {
13923 args.
path = installed_assets.string();
13924 mx::system_out <<
"acmx2: Using installed assets relative to executable: " << args.
path <<
"\n";
13927 mx::system_out <<
"acmx2: Path name not provided, using current path...\n";
13932 const char *env_shader_path = std::getenv(
"ACMX2_SHADER_PATH");
13933 if (env_shader_path && env_shader_path[0] !=
'\0') {
13934 args.
library = env_shader_path;
13936 mx::system_out <<
"acmx2: Using ACMX2_SHADER_PATH environment variable: " << args.
library <<
"\n";
13947 mx::system_err <<
"acmx2: Error: --remove-broken requires a shader library path\n";
13948 mx::system_err.flush();
13949 return EXIT_FAILURE;
13952 mx::system_err <<
"acmx2: Error: No library.json or index.txt found at: " << args.
remove_broken_path <<
"\n";
13953 mx::system_err.flush();
13954 return EXIT_FAILURE;
13957#if defined(__linux__) || defined(_WIN32)
13963 SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS,
"0");
13965 mx::system_out <<
"acmx2: remove-broken hidden build window enabled\n";
13968 mx::system_out <<
"acmx2: Creating scan window for remove-broken...\n";
13980 class RemoveBrokenWindow :
public gl::GLWindow {
13983 std::string lib_path;
13985 std::string assets_path;
13986 bool success =
false;
13988 bool active =
true;
13990 RemoveBrokenWindow(
const std::string &path,
bool is3d,
const std::string &assets,
const OpenGLContextConfig &context_config,
bool headless) : gl::GLWindow(
"ACMX2 Remove-Broken", 640, 480,
false, gl::GLMode::DESKTOP, context_config.
major, context_config.
minor,
false), lib_path(path), enable_3d(is3d), assets_path(assets) {
13992 SDL_HideWindow(getWindow());
13994 util.path = assets_path;
13998 void draw()
override {
13999 glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
14000 glClear(GL_COLOR_BUFFER_BIT);
14003 std::string vert_2d = util.getFilePath(
"data/vert.glsl");
14004 std::string vert_3d = util.getFilePath(
"data/vertex.glsl");
14005 mx::system_out <<
"acmx2: Scanning library: " << lib_path <<
"\n";
14006 mx::system_out <<
"acmx2: Mode: " << (enable_3d ?
"2D+3D" :
"2D only") <<
"\n";
14007 mx::system_out <<
"acmx2: OpenGL Renderer: " <<
safeGLString(GL_RENDERER) <<
"\n";
14008 mx::system_out <<
"acmx2: OpenGL Version: " <<
safeGLString(GL_VERSION) <<
"\n";
14017 void event(SDL_Event &)
override {}
14022 while (SDL_PollEvent(&ev)) {
14023 if (ev.type == SDL_QUIT)
14032#if defined(__linux__) || defined(_WIN32)
14036 return rb_win.success ? EXIT_SUCCESS : EXIT_FAILURE;
14041 return rb_win.success ? EXIT_SUCCESS : EXIT_FAILURE;
14042 }
catch (
const mx::Exception &e) {
14043 mx::system_err <<
"acmx2: Remove-broken failed: " << e.text() <<
"\n";
14044 mx::system_err.flush();
14045 return EXIT_FAILURE;
14046 }
catch (std::exception &e) {
14047 mx::system_err <<
"acmx2: Remove-broken failed: " << e.what() <<
"\n";
14048 mx::system_err.flush();
14049 return EXIT_FAILURE;
14055 mx::system_err <<
"acmx2: Error: --build requires a shader library path\n";
14056 mx::system_err.flush();
14057 return EXIT_FAILURE;
14060 mx::system_err <<
"acmx2: Error: No library.json or index.txt found at: " << args.
build_library_path <<
"\n";
14061 mx::system_err.flush();
14062 return EXIT_FAILURE;
14066#if defined(__linux__) || defined(_WIN32)
14072 SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS,
"0");
14074 mx::system_out <<
"acmx2: hidden cache-build window enabled\n";
14077 mx::system_out <<
"acmx2: Creating build window...\n";
14093 class BuildWindow :
public gl::GLWindow {
14096 std::string lib_path;
14098 std::string assets_path;
14099 bool success =
false;
14100 bool build_done =
false;
14101 bool active =
true;
14109 BuildWindow(
const std::string &path,
bool is3d,
const std::string &assets,
int tex_cache_size,
bool use_array,
const OpenGLContextConfig &context_config,
bool headless) : gl::GLWindow(
"ACMX2 Shader Builder", 640, 480,
false, gl::GLMode::DESKTOP, context_config.
major, context_config.
minor,
false), lib_path(path), enable_3d(is3d), assets_path(assets) {
14111 SDL_HideWindow(getWindow());
14113 mx::system_out <<
"acmx2: Window created, setting up...\n";
14115 util.path = assets_path;
14117 library.
setCacheSize(tex_cache_size > 0 ? tex_cache_size : 8);
14128 void draw()
override {
14129 glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
14130 glClear(GL_COLOR_BUFFER_BIT);
14136 static constexpr const char *kBldLogoVert =
"#version 330 core\n"
14137 "layout(location = 0) in vec3 aPos;\n"
14138 "layout(location = 1) in vec2 aTex;\n"
14140 "void main() { gl_Position = vec4(aPos, 1.0); tc = aTex; }\n";
14141 static constexpr const char *kBldLogoFrag =
"#version 330 core\n"
14143 "out vec4 color;\n"
14144 "uniform sampler2D samp;\n"
14145 "void main() { color = texture(samp, tc); }\n";
14146 gl::ShaderProgram logo_sh;
14147 gl::GLSprite logo_sp;
14148 std::string logo_path = util.getFilePath(
"data/logo.png");
14149 if (std::filesystem::exists(logo_path)) {
14150 GLuint logo_tex = 0;
14152 int lw = 0, lh = 0;
14153 logo_tex = gl::loadTexture(logo_path, lw, lh);
14154 if (logo_tex && logo_sh.loadProgramFromText(kBldLogoVert, kBldLogoFrag)) {
14155 logo_sp.initSize(w, h);
14156 logo_sp.setName(
"samp");
14157 logo_sp.setShader(&logo_sh);
14158 float scale = std::min(
static_cast<float>(w) / lw,
static_cast<float>(h) / lh);
14159 int dw =
static_cast<int>(lw * scale);
14160 int dh =
static_cast<int>(lh * scale);
14161 int lx = (w - dw) / 2;
14162 int ly = (h - dh) / 2;
14163 logo_sp.initWithTexture(&logo_sh, logo_tex, lx, ly, dw, dh);
14170 glDeleteTextures(1, &logo_tex);
14176 std::string vert_2d = util.getFilePath(
"data/vert.glsl");
14177 std::string vert_3d = util.getFilePath(
"data/vertex.glsl");
14179 mx::system_out <<
"acmx2: Building shader cache for: " << lib_path <<
"\n";
14180 mx::system_out <<
"acmx2: Mode: " << (enable_3d ?
"2D+3D" :
"2D only") <<
"\n";
14181 mx::system_out <<
"acmx2: OpenGL Renderer: " <<
safeGLString(GL_RENDERER) <<
"\n";
14182 mx::system_out <<
"acmx2: OpenGL Version: " <<
safeGLString(GL_VERSION) <<
"\n";
14193 void event(SDL_Event &e)
override {}
14203 while (SDL_PollEvent(&ev)) {
14204 if (ev.type == SDL_QUIT) {
14214#if defined(__linux__) || defined(_WIN32)
14217 build_win.buildLoop();
14218 return build_win.success ? EXIT_SUCCESS : EXIT_FAILURE;
14222 build_win.buildLoop();
14224 return build_win.success ? EXIT_SUCCESS : EXIT_FAILURE;
14225 }
catch (
const mx::Exception &e) {
14226 mx::system_err <<
"acmx2: Build failed: " << e.text() <<
"\n";
14227 mx::system_err.flush();
14228 return EXIT_FAILURE;
14229 }
catch (std::exception &e) {
14230 mx::system_err <<
"acmx2: Build failed: " << e.what() <<
"\n";
14231 mx::system_err.flush();
14232 return EXIT_FAILURE;
14240 if (selectedIndex < 0) {
14241 mx::system_err <<
"acmx2: Shader file is not present in the active library: " << args.
shader_file <<
"\n";
14242 return EXIT_FAILURE;
14250 if (passIndex < 0) {
14251 mx::system_err <<
"acmx2: Shader pass file is not present in the active library: " << shaderFile <<
"\n";
14252 return EXIT_FAILURE;
14263 mx::system_err <<
"acmx2: Error: --silent mode requires a video (-i/--input) "
14264 "or graphics (-g/--graphic) input file\n";
14265 mx::system_err <<
" Silent mode does not support camera input.\n";
14266 mx::system_err.flush();
14267 return EXIT_FAILURE;
14270 mx::system_err <<
"acmx2: Error: --silent mode requires an output file (-o/--output)\n";
14271 mx::system_err.flush();
14272 return EXIT_FAILURE;
14275 mx::system_err <<
"acmx2: Error: silent graphics mode requires a positive "
14276 "maximum duration (--duration <seconds>)\n";
14277 mx::system_err.flush();
14278 return EXIT_FAILURE;
14280 mx::system_out <<
"acmx2: Silent mode enabled - processing without window\n";
14286 std::setvbuf(stdout,
nullptr, _IOLBF, 0);
14300 SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS,
"0");
14302 mx::system_out <<
"acmx2: Headless: using a hidden native Windows OpenGL "
14305 mx::system_out <<
"acmx2: Headless: SDL_VIDEODRIVER=" << (getenv(
"SDL_VIDEODRIVER") ? getenv(
"SDL_VIDEODRIVER") :
"(unset)") <<
", SDL_AUDIODRIVER=" << (getenv(
"SDL_AUDIODRIVER") ? getenv(
"SDL_AUDIODRIVER") :
"(unset)") <<
"\n";
14311 mx::system_out <<
"acmx2: Headless: signal handlers installed\n";
14315 mx::system_err <<
"acmx2: Error: --png in video-file mode requires -o/--output to derive the PNG frame directory\n";
14316 mx::system_err.flush();
14317 return EXIT_FAILURE;
14325 const cv::Mat graphic_size_probe = cv::imread(args.
graphic_file);
14326 if (graphic_size_probe.empty()) {
14327 mx::system_err <<
"acmx2: Error: graphics file not found or unreadable: " << args.
graphic_file <<
"\n";
14328 mx::system_err.flush();
14329 return EXIT_FAILURE;
14332 args.
tw = graphic_size_probe.cols;
14333 args.
th = graphic_size_probe.rows;
14335 std::swap(args.
tw, args.
th);
14337 mx::system_out <<
"acmx2: Graphics window initial size: " << args.
tw <<
"x" << args.
th <<
"\n";
14338 }
else if (!args.
filename.empty() && !args.
sizev.has_value()) {
14340 if (video_size.has_value()) {
14341 args.
tw = video_size->width;
14342 args.
th = video_size->height;
14344 std::swap(args.
tw, args.
th);
14346 mx::system_out <<
"acmx2: Video window initial size: " << args.
tw <<
"x" << args.
th <<
"\n";
14348 mx::system_out <<
"acmx2: Could not probe video dimensions before window "
14349 "creation; using the startup fallback size\n";
14353 SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR,
"0");
14354 SDL_SetHint(
"SDL_VIDEO_WAYLAND_WMCLASS",
"acmx2");
14355 SDL_SetHint(
"SDL_VIDEO_X11_WMCLASS",
"ACMX2");
14360 MainWindow main_window(args, context_config,
true);
14361 main_window.loop();
14363 MainWindow main_window(args, context_config);
14364 main_window.loop();
14366 }
catch (
const mx::Exception &e) {
14367 mx::system_err <<
"acmx2: Exception: " << e.text() <<
"\n";
14368 mx::system_err.flush();
14369 return EXIT_FAILURE;
14370 }
catch (std::exception &e) {
14371 mx::system_err <<
"acmx2: Exception: " << e.what() <<
"\n";
14372 mx::system_err.flush();
14373 return EXIT_FAILURE;
14375 mx::system_err <<
"acmx2: Unknown exception occurred.\n";
14376 mx::system_err.flush();
14377 return EXIT_FAILURE;
14379 return EXIT_SUCCESS;
float hlgToLinearScalar(float e)
void transfer_audio(std::string_view, std::string_view)
Copy the audio track from one media file to another via FFmpeg.
static bool isEditorPreviewForShader(const std::filesystem::path &loaded_shader, const std::filesystem::path &requested_path)
GLsizei GLsizei GLenum void * binary
static std::string shaderManifestPath(const std::string &library_path)
static OpenGLContextConfig select_open_gl_context()
void checkDevices(bool list_only=false)
Verify CUDA device availability and print GPU info.
static bool isEditorPreviewPath(const std::filesystem::path &library_path, const std::filesystem::path &requested_path)
constexpr const char * kDisplayFragPassthrough
bool convertBt2020Yuv10LimitedToRgba16(const AVFrame *src, cv::Mat &out)
constexpr const char * kHdrEncodeFrag
static std::optional< cv::Size > probe_video_size(const std::string &filename)
Read a video's coded dimensions without opening a decoder.
int main(int argc, char **argv)
Application entry point.
void printBranding()
Print the program name, software brand, and project URL once.
static bool compute_shader_supported
True when the active ACMX2 context can run OpenGL compute shaders.
static void update_compute_shader_support()
Update the feature flag from the real context rather than only the probe.
float pqToLinearScalar(float e)
static std::optional< std::string > normalizeShaderIndexEntry(const std::string &raw)
std::vector< uint16_t > toneMapHdrRgba16ToSrgbRgba16(const unsigned char *hdr_pixels, int w, int h, int hdr_trc)
static std::filesystem::path installed_assets_directory()
Locate the installed ACMX2 asset root relative to the executable.
static bool isValidCustomUniformName(const std::string &name)
static bool isComputeShaderFile(const std::string &path)
static bool writeJsonShaderManifest(const std::string &path, const std::vector< std::string > &entries, const std::vector< ShaderManifestData::CustomUniform > &custom_uniforms, std::string &error)
static std::string safeGLString(GLenum name)
static void print_open_gl_uniform_limits()
Print the active driver's shader-uniform capacity in one line.
constexpr std::size_t FFT_SIZE
static PFNGLPROGRAMBINARYPROC_LOCAL glProgramBinaryFunc
static bool loadShaderManifest(const std::string &library_path, ShaderManifestData &manifest, std::string &error)
constexpr const char * kMuxOverlayFrag
static bool collectShaderLibraryEntries(const std::string &library_path, std::vector< std::string > &shader_files, std::string &error)
void printAbout(bool include_branding=true)
Print program information, arguments, and keyboard controls.
static std::vector< std::string > sortedShaderLibraryEntries(const std::string &library_path)
static bool resolveShaderPathInLibrary(const std::string &library_path, const std::string &relative_path, std::string &resolved_full_path)
static std::filesystem::path executable_directory()
Return the directory containing the running ACMX2 executable.
GLsizei GLsizei GLenum * binaryFormat
static PFNGLGETPROGRAMBINARYPROC_LOCAL glGetProgramBinaryFunc
constexpr const char * kHdrVertPassthrough
constexpr const char * kHdrDecodeFrag
std::vector< unsigned char > toneMapHdrRgba16ToSdrRgba8(const std::vector< unsigned char > &hdr_pixels, int w, int h, int hdr_trc)
uint16_t linearToSrgb16(float v)
static bool set_environment_if_missing(const char *name, const char *value)
unsigned char linearToSrgb8(float v)
static int shaderIndexForFile(const std::vector< std::string > &shader_files, const std::string &shader_file)
bool loadProgramBinaryFunctions()
Dynamically load glGetProgramBinary / glProgramBinary via SDL.
constexpr const char * kDisplayVertFlip
typedef void(APIENTRYP PFNGLGETPROGRAMBINARYPROC_LOCAL)(GLuint program
double file_audio_playback_time()
Return the current output-device playback timestamp in seconds.
bool file_audio_has_output_clock()
Check whether real-time output playback can provide the master clock.
void file_audio_close()
Stop file-audio playback and release decoder/sample resources.
std::vector< std::string > file_audio_source_paths()
Return the successfully decoded source tracks in playback order.
bool file_audio_open(const std::string &filepath)
Open and fully decode an audio file or M3U playlist to mono float PCM at 44.1 kHz.
void file_audio_set_repeat(bool enabled)
Enable or disable looping of the currently decoded audio file.
bool file_audio_enable_output(int output_device)
Configure real-time playback of the decoded file through an output device.
bool file_audio_is_active()
Return true while decoded file-audio samples remain.
void file_audio_process_frame(double video_fps, acmx2::audio::AudioAnalyzer &analyzer)
Advance one video-frame worth of samples and update audio analysis.
std::string file_audio_current_source_path()
Return the source track at the current playback position.
File-based audio input for audio-reactive shaders.
Core rendering object—drives the capture → filter → shade → record pipeline.
float audio_warmup_envelope
Startup fade for audio-driven uniforms/textures.
bool autopilot_sequential
When true, autopilot advances through the playlist in order instead of randomly (toggle via SDLK_y).
void runFileAudioMuxSync()
Run ffmpeg synchronously to mux the audio file into the output video.
std::atomic< bool > muxComplete
uint64_t recording_pbo_timeline_frame[2]
void pollMidi(gl::GLWindow *win)
Drain all pending MIDI messages and dispatch mapped actions.
void convertResourcesToHdr(int w, int h)
One-time conversion of existing GL resources from SDR (8-bit) to HDR (16-bit) formats.
void ensureHdrResources(int w, int h)
Lazily allocate the HDR decode/encode textures, FBOs and shader programs.
int64_t getFrameCount()
Return the current frame count (writer count or display count).
bool autopilot_random_crossfade
When true, autopilot picks a random crossfade shader on each switch.
void cleanupMidi()
Close the MIDI input port and free the RtMidiIn instance.
void resetAutopilotInterval()
std::atomic< bool > running
void updateTextureRGBA(GLuint texture, cv::Mat &frame)
Update a GL texture with pre-converted RGBA data (no colour conversion).
glm::vec3 modelCenterOffset
float updateAudioWarmupEnvelope()
Advance and return the startup warmup envelope in [0,1].
void runMuxSync()
Run ffmpeg synchronously to mux the recorded audio WAV into the video MP4.
std::atomic< uint64_t > generate_all_frames
uint64_t last_graphics_progress_frame
std::condition_variable queueCondVar
void runHdrEncodePass(GLuint src_tex, int w, int h)
Run the linear BT.2020 -> PQ/HLG encode pass from src_tex into hdr_encoded_texture.
void appendRecordingTitleSuffix(std::ostringstream &stream)
std::optional< uintmax_t > getOutputFileSizeBytes() const
int hdr_upload_tex_w
Current GL size of camera_texture in HDR upload mode.
void autopilotRandomSwitch(gl::GLWindow *win)
Pick a random entry from the active playlist and apply it.
std::chrono::steady_clock::time_point lastMidiButtonTime
int watermark_b
Watermark color blue.
std::atomic< bool > isMuxing
std::condition_variable captureQueueCondVar
std::string audio_record_file
void bindPassHistoryTextures(size_t shader_index)
Bind history textures required by a cache-aware pass.
bool autopilot_random_interval
When true, choose a new interval from [4, autopilot_random_timeout] after each switch.
int crossfadeShaderIndex
Active index into crossfadeShaders.
void startCaptureThread()
Start the background camera capture thread (camera mode only).
std::vector< PlaylistNode > playlist_tree
std::chrono::steady_clock::time_point last_video_progress_emit
void beginCrossfade(gl::GLWindow *win)
Start a crossfade transition from the current frame.
void generateRandomMultipass(gl::GLWindow *win)
GLuint hdr_encoded_texture
GL_RGBA16: final PQ-re-encoded output for readback.
ac_gpu::GPUFilter * d_filterList
std::map< std::pair< unsigned char, unsigned char >, int > knobFrameCount
std::optional< cv::Size > sizev
std::chrono::steady_clock::time_point sessionStartTime
SnapshotThreadPool snapshot_pool
void updateShaderNameCache()
Refresh the cached shader name string for the HUD overlay.
GLuint hdr_linear_video_texture
GL_RGBA16F: PQ/HLG-decoded linear BT.2020 video.
int autopilot_counter
Frames elapsed since last autopilot switch.
FFMpegVideoReader ffmpeg_reader
void flushPBOs(gl::GLWindow *win)
Read any remaining pixels from both PBOs and enqueue them.
std::atomic< bool > copy_audio
SpectrumTexture spectrumTex
1D FFT magnitude texture for shaders.
GLuint preOverlayFBO
FBO whose color attachment is preOverlayTexture; used to save/restore fboTexture around the overlay+w...
std::chrono::steady_clock::time_point lastFrameTime
std::optional< cv::Size > sizec
gl::ShaderProgram fshader3d
gl::ShaderProgram muxOverlayShader
void stopCaptureThread()
Signal the capture thread to stop and wait for it to join.
std::string watermark_text
Active watermark text (overridden by –use-watermark).
void emitSilentGraphicsProgress(bool complete)
Emit bounded progress updates for duration-limited headless graphics rendering.
void generateRandomMultipassLong(gl::GLWindow *win)
std::vector< MidiCode > midiCodes
int watermark_r
Watermark color red.
gl::ShaderProgram hdr_decode_shader
PQ/HLG -> linear BT.2020 fullscreen pass.
std::atomic< bool > finished
bool recording_pbo_uses_timeline_clock[2]
void runHdrDecodePass(int w, int h)
Run the PQ/HLG -> linear BT.2020 decode pass, sampling camera_texture and writing into hdr_linear_vid...
std::vector< int > saved_pass_list_before_random
std::thread captureThread
GLuint crossfadePrevTexture
Snapshot of the previous frame used as the blend source.
void appendSilentProgressFileSize(std::ostream &stream) const
Append the current encoded output size to a silent progress line.
std::atomic< bool > skip_audio_mux_on_exit
gl::ShaderProgram fshader
bool rotation_swaps_dimensions() const
std::chrono::steady_clock::time_point last_graphics_progress_emit
int audio_buffer_count
Allocated history-array layer count.
bool input_is_hdr
Active HDR pipeline for this input.
bool spectrum_scale_by_sense
When true, scale spectrum 1D buffer by audio sensitivity.
uint64_t decoded_video_frame_count
void autopilotSequentialAdvance(gl::GLWindow *win)
Advance to the next entry in the active playlist (wrapping).
int watermark_g
Watermark color green.
unsigned int frame_counter
std::string playlist_file
float crossfadeDuration
Duration of the crossfade transition in seconds.
std::atomic< uint64_t > frames_dropped
size_t saved_shader_index_before_random
int hdr_upload_tex_h
Current GL size of camera_texture in HDR upload mode.
bool random_multipass_mode
std::string getTimeString()
Format the current session time as HH:MM:SS.
EncodeOptions encode_opts
bool file_audio_mode
True when audio comes from a file instead of RtAudio.
SDL_Keycode midiKeyToSDL(int code)
Convert an ACMX2 virtual key code to an SDL_Keycode.
std::vector< ac_gpu::Filter > gpu_filters
std::queue< FrameData > frameQueue
std::atomic< uint64_t > generate_saved_counter
std::string lastMidiButton
GLuint crossfadeFBO
FBO used for the crossfade compositing pass.
int last_progress_percent
bool recording_pbo_primed
void uploadHdrFrame(const cv::Mat &rgba16)
Upload a 16-bit-per-channel RGBA frame (from FFMpegVideoReader::readHdr()) into camera_texture.
std::atomic< uint64_t > snapshotOffset
void startMediaTimelineIfReady()
Start playback and recording timing on the first valid source frame.
std::string audio_file_path
Path to the audio file used for file_audio_mode.
virtual void event(gl::GLWindow *win, SDL_Event &e) override
Handle SDL keyboard events (shader navigation, mode toggles, etc.).
virtual void load(gl::GLWindow *win) override
Called once by the GLWindow to initialise capture, shaders, FBOs, and recording.
void updateTexture(GLuint texture, cv::Mat &frame)
Update an existing GL texture with new cv::Mat data (BGR → RGBA).
FrameRotation frame_rotation
std::mt19937 autopilot_rng
std::vector< int > shader_pass_list
unsigned char ** d_ptrList
std::atomic< bool > captureRunning
int gpu_current_filter_index
double max_size_limit_bytes
std::mutex captureQueueMutex
void ensureCrossfadeFBO(int width, int height)
Lazily create the crossfade FBO and its two textures.
bool use_shader_cache_flag
std::string cached_shader_name
float audio_warmup_rate
Warmup envelope slope in 1/sec (higher = faster ramp).
static const char * midiKeyName(int code)
Map a virtual key code to a human-readable short name.
gl::ShaderProgram display_flip_shader
Display shader with optional Y-flip for windowed output.
std::map< std::pair< unsigned char, unsigned char >, unsigned char > knobState
~ACView() override
Destroy the rendering object: stop threads, release GPU resources.
std::chrono::steady_clock::time_point fpsLastTime
float crossfadeAlpha
Current blend factor (0 = old frame, 1 = new frame).
int autopilot_frames
Frames between random switches in autopilot mode (0 = unset).
virtual void draw(gl::GLWindow *win) override
Called every frame: capture, GPU-filter, shade, composite, record, display overlay.
int activePlaylistSize() const
std::chrono::steady_clock::time_point audio_warmup_last_tick
bool crossfadeActive
True while a crossfade transition is in progress.
void ensurePassTargets(gl::GLWindow *win)
Allocate the shared fragment/compute ping-pong targets on demand.
void stopWriterThread()
Stop the writer thread, close the Writer, and handle audio transfer.
std::tuple< int, std::string, int > flib
bool audio_repeat_mode
When true, restart file audio when its samples are exhausted.
bool audio_trunc_mode
When true, stop playback when file audio samples are exhausted.
void startAudioRecordingIfNeeded()
Start WAV audio capture if recording is configured and not already active.
std::map< std::pair< unsigned char, unsigned char >, unsigned char > knobPrevValue
void beginMuxing(gl::GLWindow *win)
Launch the asynchronous audio-mux thread and show a progress overlay.
void generateRandomMultipassShort(gl::GLWindow *win)
SpectrumHistory spectrumHistory
Runtime-sized FFT history array.
std::string png_video_dir
float cameraRotationSpeed
std::atomic< uint64_t > png_video_frame_counter
std::vector< std::string > crossfadeShaderNames
Display names matching crossfadeShaders by index.
void maybeRandomizeAutopilotCrossfade()
GLuint hdr_linear_video_fbo
FBO writing into hdr_linear_video_texture.
void setupCaptureFBO(int width, int height)
Create the off-screen FBO used for capture/recording.
void hdrReadback(GLuint src_linear_tex, int w, int h, std::vector< unsigned char > &out)
HDR readback: run the encode pass and read 16-bit PQ-encoded RGB pixels back into out.
void rotate_frame(cv::Mat &frame)
GLuint crossfadeTexture
Colour attachment of crossfadeFBO (blended output).
std::vector< gl::ShaderProgram > crossfadeShaders
Available crossfade transition shaders (cycle with [ and ]).
GLuint hdr_encoded_fbo
FBO writing into hdr_encoded_texture.
GLuint loadTexture(cv::Mat &frame)
Create a new GL texture from a cv::Mat (BGR → RGBA upload).
void injectKey(SDL_Keycode key, gl::GLWindow *win)
Synthesise an SDL_KEYUP event and dispatch it through event().
acmx2::audio::AudioEngine audio_engine
int autopilot_interval_frames
Active interval currently used by autopilot tick.
void drawMidiOverlay(gl::GLWindow *win, mx::Font &font, int startY)
Draw the MIDI status overlay (active indicator, knob bars, last button).
bool media_timeline_started
bool needsMux()
Determine if recorded-audio mux should run at shutdown.
void initMidi(const std::string &mapFile, int deviceIndex)
Open a MIDI input port and load the key→action mapping file.
cv::Mat hdr_frame_mat
Scratch CV_16UC4 RGBA frame for HDR decode.
void resetAudioWarmupEnvelope()
Reset the startup envelope used to tame initial audio intensity.
std::vector< int > playlist_indices
std::unique_ptr< ac_gpu::DynamicFrameBuffer > gpu_frame_buffer
std::chrono::steady_clock::time_point media_timeline_start_time
bool autopilot_enabled
Toggle autopilot via SDLK_j when playlist is enabled.
void startWriterThread()
Start the background writer thread (video recording + snapshots).
TextureUploader tex_uploader
std::vector< int > saved_pass_list
bool needsAsyncShutdown()
int hdr_resource_w
Width of HDR intermediate/encoded textures.
int autopilot_random_timeout
Inclusive upper bound for random autopilot interval.
bool runComputePass(gl::GLWindow *win, size_t shader_index, GLuint input_texture, GLuint output_texture)
std::atomic< bool > writerRunning
bool needsTransferAudio()
Determine if source-track audio copy should run at shutdown.
void emitSilentVideoProgress(bool complete)
Emit progress for headless video rendering.
int hdr_resource_h
Height of HDR intermediate/encoded textures.
ACView(const MXArguments &args)
Construct the rendering object from parsed CLI arguments.
GLuint preOverlayTexture
Snapshot of fboTexture taken before drawing the watermark/display-filter overlay, used to restore the...
int input_hdr_trc
AVColorTransferCharacteristic (PQ/HLG/BT2020).
gl::ShaderProgram hdr_encode_shader
Linear BT.2020 -> PQ (or HLG) fullscreen pass.
gl::GLSprite muxOverlaySprite
std::queue< cv::Mat > captureQueue
void applyCrossfade(gl::GLWindow *win, GLuint currentTexture)
Render the crossfade blend for the current frame.
bool saved_pass_enabled_before_random
std::chrono::steady_clock::time_point crossfadeStartTime
Wall-clock time the current crossfade began.
const std::vector< uint8_t > & getHdrMasteringDisplay() const
bool readHdr(cv::Mat &out_rgba16)
AVPixelFormat sws_src_format_hdr
AVCodecContext * codec_ctx
int getHdrPrimaries() const
std::vector< uint8_t > hdr_content_light
AVFormatContext * format_ctx
bool isHwDecodeEnabled() const
bool read(cv::Mat &out_bgr)
bool skip()
Decode and discard one frame without transferring or converting it.
static AVPixelFormat getHwFormat(AVCodecContext *ctx, const AVPixelFormat *pix_fmts)
int getHdrBitDepth() const
void enableCudaHwDecode(const AVCodec *codec)
int getHdrColorRange() const
int getHdrColorspace() const
int64_t getCurrentFrame() const
double getFrameCount() const
AVBufferRef * hw_device_ctx
int getHdrTransfer() const
AVPixelFormat sws_src_format
const std::vector< uint8_t > & getHdrContentLight() const
SwsContext * sws_ctx_hdr
Separate SwsContext for the HDR read path (RGBA64LE output).
bool open(const std::string &filename, bool prefer_cuda)
std::vector< uint8_t > hdr_mastering_display
Fixed-capacity ring buffer of GL textures for temporal shaders.
void pushFromTexture(GLuint src_tex, int w, int h)
GPU-side push from an existing GL texture into the next ring slot.
std::vector< GLuint > textures
bool isFull() const
True when the ring is fully populated.
std::size_t size() const
Number of frames currently retained (0 ≤ n ≤ capacity).
GLuint historyTexture() const
Array texture containing the physical history ring layers.
void pushFromFBO(GLuint src_fbo, int w, int h)
GPU-side push from a source FBO into the next ring slot.
GLuint textureAt(std::size_t index) const
Return the GL texture at logical index index.
GLuint history_texture
Optional array-backed history ring.
void fill(const cv::Mat &frame)
Pre-fill every slot with copies of a single frame.
FrameCache(const FrameCache &)=delete
FrameCache & operator=(const FrameCache &)=delete
int oldestLayer() const
Physical array layer corresponding to logical history index zero.
void push(const cv::Mat &frame)
Upload frame (BGR) into the next ring slot.
void allocateHistoryTexture()
Allocate the optional array texture using the cache's dimensions and pixel format.
void init(int w, int h, bool hdr=false)
Allocate the ring of GL textures sized to w x h.
GLuint scratch_fbo
Scratch FBO used by pushFromTexture().
void cleanup()
Release all GL textures and reset the ring state.
std::size_t capacity() const
Maximum number of frames retained by the ring.
FrameCache(std::size_t num, bool use_array=false)
void draw() override
Per-frame callback: clear, draw ACView, swap buffers.
static int eventFilter(void *userdata, SDL_Event *event)
MainWindow(const MXArguments &args, const OpenGLContextConfig &context_config)
Construct a visible (windowed) MainWindow.
void event(SDL_Event &e) override
Placeholder—SDL events are forwarded to ACView::event() by libmx2.
void initCommon(const MXArguments &args)
MainWindow(const MXArguments &args, const OpenGLContextConfig &context_config, bool headless)
Construct a headless (off-screen) MainWindow for silent batch processing.
Manages the complete collection of compiled GLSL shader programs.
void setAudioWarmupEnvelope(float env)
Set startup audio warmup envelope in [0,1] for uniform scaling.
bool get3D() const
Return true if currently in 3D mode.
void loadProgram(gl::GLWindow *win, const std::string text)
Compile a single fragment shader with the 2D (and optionally 3D) vertex shader.
std::string getFullShaderName(const std::vector< int > &pass_list)
Get a display string including the active shader and the pass list.
bool loadFromCache(gl::GLWindow *win, const std::string &library_path, mx::Font &loadingFont, const std::string &vert_2d="", const std::string &vert_3d="")
Attempt to load all shader programs from the binary cache file.
float getAmpUntouched() const
size_t size()
Number of programs in the currently active set (2D or 3D).
bool isCompute(size_t idx) const
Return whether a library slot contains an executable compute program.
bool loadProgramWithSize(gl::ShaderProgram *prog, const std::string &vert_path, const std::string &frag_path) const
Compile a shader from file pair, injecting #define SIZE N into the fragment source.
std::unordered_map< int, ProgramData > program_names_2d
void is3D(bool is3d)
Switch between the 2D and 3D program vectors.
void enableDualMode(bool enable)
Enable or disable dual-mode compilation (2D + 3D vertex shaders).
void setFPS(float fps_value)
Upload the current frame-rate to the active shader's iFrameRate uniform.
void setHistoryHead(int layer)
Set the physical array layer that represents logical history zero.
void setMidiSlider(int idx, float val)
Set a MIDI slider value (index 0–3, value 0.0–1.0).
std::unique_ptr< gl::ShaderProgram > makePassthroughProgram(const std::string &vert_path)
Compile a minimal passthrough fragment shader as a stand-in.
void setSpectrumHistoryHead(int layer)
Set the physical array layer containing the newest FFT frame.
gl::ShaderProgram * getShader(size_t idx)
Get a shader by index from the active set (2D or 3D).
int cache_size
Active runtime size of the texture-cache ring buffer.
std::vector< std::regex > custom_uniform_references
void update(gl::GLWindow *win)
Per-frame update: advance time_f, upload all uniforms to the active shader.
std::string injectShaderSize(const std::string &frag_path, int size, bool use_array) const
Read a fragment shader file and inject #define SIZE N immediately after its #version directive.
void setupProgramUniforms(gl::GLWindow *win, gl::ShaderProgram *prog, std::unordered_map< int, ProgramData > &names, size_t pos, const std::string &text)
Query and store all uniform locations for a compiled shader program.
std::unique_ptr< gl::ShaderProgram > compileProgramForReload(const std::string &vert_path, const std::string &frag_path, std::string &error) const
Compile a replacement program while retaining complete driver diagnostics.
gl::ShaderProgram * getShader3D(size_t idx)
Get a 3D shader by index (regardless of current 2D/3D mode).
void inc()
Advance to the next shader in the library (clamped at the end).
std::vector< std::unique_ptr< gl::ShaderProgram > > programs_3d
void updateShaderUniforms(gl::GLWindow *win, size_t idx)
Upload all uniforms for an arbitrary shader in the active set.
float midi_slider[4]
MIDI CC slider values (0.0–1.0) for shader uniforms slider1–slider4.
int audioBufferCount() const
Number of spectrum history array layers currently configured.
std::unordered_map< int, ProgramData > program_names_3d
float audio_warmup_envelope
Startup ramp [0..1] used to soften initial audio-reactive intensity.
bool isCache2D(size_t idx) const
Check whether the 2D shader at idx is a texture-cache shader.
void event(SDL_Event &e)
Reserved for future SDL event handling inside the library.
void dec()
Move to the previous shader in the library (clamped at zero).
size_t size2d()
Number of 2D programs.
std::string getShaderNameByIndex(size_t idx)
Get the stem name of a shader by its numeric index.
int spectrum_history_head
Physical layer containing the newest spectrum.
void loadProgramsWithCache(gl::GLWindow *win, const std::string &text, mx::Font &loadingFont)
Try loading a full shader library from the binary cache, with fallback.
void setUniform(const std::string &name, int value)
Bind a cache sampler slot for texture-cache shaders.
std::unique_ptr< gl::ShaderProgram > makeComputeProgram(const std::string &compute_path, std::string &error) const
Compile and link a standalone OpenGL compute program.
void setVideoFPS(double fps)
Set the frame rate used by normalized time advancement.
void toggleAudioDelta()
Toggle delta-time scaling for audio-reactive mode.
void setTimeSpeed(float speed)
Set the absolute time_f advancement speed multiplier.
void setNormalizedTime(bool enabled)
Select fixed-per-frame or elapsed real-time advancement.
size_t size3d()
Number of 3D programs.
uint64_t preparedFragmentHash(const std::string &frag_path) const
void uploadAcidCamUniforms(const ProgramData &n, size_t idx)
void decTimeSpeed(float step)
Decrease the time_f speed multiplier by step, clamped to zero.
void setCustomUniformValues(const std::vector< ShaderManifestData::CustomUniform > &uniforms)
bool isCache()
Check whether the currently active shader is a texture-cache shader.
void clear()
Remove all compiled shader programs and reset the library index.
bool isBypassed() const
Return true if shader processing is currently bypassed.
void activeTime(bool t)
Enable or disable automatic wall-clock time advancement.
void enableCache(bool enable)
Enable or disable use of the ac::ShaderProgram binary-cache wrapper.
void useProgram()
Activate the current shader program on the GPU (glUseProgram).
int findShaderByName(const std::string &name)
Look up a shader index by its stem name.
void setAudioBufferCount(int n)
Configure the runtime depth of spectrum_history.
void uploadCustomUniforms(const ProgramData &data) const
Upload configured custom uniforms to the GPU for the given program.
void toggleBypass()
Toggle the shader bypass flag.
void stepAcidCamUniforms()
Advance acidcamGL-compatible colour-alpha oscillators and random values.
void decTime(float value)
Step time_f backward manually (when auto-time is paused).
bool buildShaderCache(gl::GLWindow *win, const std::string &library_path, const std::string &vert_2d, const std::string &vert_3d, mx::Font *loadingFont=nullptr)
Build the on-disk shader binary cache for all shaders in a library.
bool removeBrokenShaders(gl::GLWindow *win, const std::string &library_path, const std::string &vert_2d, const std::string &vert_3d)
Compile every shader in a library and rewrite its manifest without the ones that fail to compile.
bool reloadProgram(gl::GLWindow *win, size_t shader_index, const std::string &fragment_path, std::string &error)
Recompile one library slot and atomically replace it on success.
std::vector< ShaderProgramKind > program_kinds
void setIndex(size_t i)
Set the active shader by index.
std::vector< std::regex > custom_uniform_declarations
gl::ShaderProgram * shader()
Return a raw pointer to the currently active ShaderProgram.
std::vector< ShaderManifestData::CustomUniform > custom_uniforms
bool dispatchCompute2D(gl::GLWindow *win, size_t idx, GLuint input_texture, GLuint output_texture)
Run a compute library slot as a full-frame image pass.
int audio_buffer_count
Runtime depth of the spectrum history array.
void incTimeSpeed(float step)
Increase the time_f speed multiplier by step.
int cacheSize() const
Active cache ring size as seen by the shader-compile path.
void incTime(float value)
Step time_f forward manually (when auto-time is paused).
size_t index()
Return the current shader index.
void setCacheSize(int size)
Set the active texture-cache ring length.
std::string getFullShaderName()
Get a display string for the currently active shader.
std::unique_ptr< gl::ShaderProgram > makeProgram()
void toggle3D()
Toggle between 2D and 3D rendering modes.
const acmx2::audio::AudioAnalyzer * audio_analyzer
void loadPrograms(gl::GLWindow *win, const std::string &text, mx::Font &loadingFont)
Compile every shader listed in the preferred manifest.
void audioTime(bool t)
Enable or disable audio-reactive time advancement.
void setHistoryTextureArray(bool enabled)
Select the cache sampler representation before compiling shaders.
ShaderLibrary(const acmx2::audio::AudioAnalyzer *analyzer=nullptr)
static std::string shaderInfoLog(GLuint shader)
bool isDualMode() const
Return whether dual mode (2D + 3D) is active.
std::vector< std::unique_ptr< gl::ShaderProgram > > programs_2d
static std::string shaderCacheFilePath(const std::string &assets_path, const std::string &library_path, int cache_size=8, bool use_array=false)
Resolve the on-disk path for the shader binary cache file.
gl::ShaderProgram * getShader2D(size_t idx)
Get a 2D shader by index (regardless of current 2D/3D mode).
static std::string programInfoLog(GLuint program)
bool compileShaderCacheEntry(const std::string &shader_file, const std::string &full_path, const std::string &vert_2d, const std::string &vert_3d, bool include_3d, ShaderCacheEntry &entry)
Compile one shader and replace a single binary-cache entry.
void updateShaderUniforms2D(gl::GLWindow *win, size_t idx)
Upload all uniforms for a shader in the 2D set specifically.
A fixed-size thread pool used for writing PNG snapshots asynchronously.
bool stop
When true, workers exit after draining the queue.
~SnapshotThreadPool()
Drain remaining tasks and join all worker threads.
std::condition_variable condition
Signalled when a task is enqueued or pool stops.
SnapshotThreadPool(size_t threads)
Construct a pool with a fixed number of worker threads.
std::vector< std::thread > workers
Persistent worker threads.
std::mutex queue_mutex
Protects tasks and stop.
void enqueue(F &&f)
Submit a callable for asynchronous execution.
std::queue< std::function< void()> > tasks
FIFO of pending PNG-write tasks.
Runtime-sized ring buffer stored in one 1D array texture.
static constexpr int TEXTURE_UNIT
Texture unit assigned to the complete spectrum history array.
GLuint texture_id
One GL_TEXTURE_1D_ARRAY object.
int write_idx
Next physical layer to overwrite.
int newestLayer() const
Physical layer containing the newest spectrum frame.
int bins
Texels per layer (== FFT_SIZE / 2).
std::vector< float > scaled_buf
Scratch for sensitivity-scaled magnitudes.
SpectrumHistory(const acmx2::audio::AudioAnalyzer &analyzer)
int layer_count
Runtime-selected array depth.
int count() const
Number of history layers currently allocated.
void cleanup()
Delete the array texture and reset state.
void update(float scale=0.0f)
Push the current FFT magnitudes into the head of the ring buffer.
int init(int requested_count)
Allocate one array texture with requested_count history layers.
const acmx2::audio::AudioAnalyzer & analyzer
void bind() const
Bind the complete spectrum history array to its single texture unit.
Manages a 1D OpenGL texture that holds the FFT frequency-magnitude spectrum.
acmx2::audio::AudioAnalyzer & analyzer
void bind(int unit=SPECTRUM_TEXTURE_UNIT) const
Bind the spectrum texture to a specific texture unit.
std::vector< float > scaled_buf
Scratch buffer for sensitivity-scaled magnitudes.
static constexpr int SPECTRUM_TEXTURE_UNIT
Texture unit reserved for the spectrum (units 0–8 are taken).
int bins
Number of texels (== FFT_SIZE / 2).
SpectrumTexture(acmx2::audio::AudioAnalyzer &analyzer)
void update()
Compute a fresh FFT and upload the magnitudes into the texture.
void init()
Create the 1D texture and set its sampling parameters.
void cleanup()
Delete the OpenGL texture and reset state.
GLuint textureID
OpenGL name for the 1D texture.
Host-zero-copy CUDA-to-OpenGL texture transfer via direct image interop.
int height
Current texture height in pixels.
GLuint textureID
OpenGL texture receiving the frame data.
int width
Current texture width in pixels.
void cleanup()
Release all GPU resources (CUDA registration, stream, texture).
void init(int w, int h)
Create (or recreate) the GL texture and CUDA image registration.
Owns the audio-reactive analysis state shared by live and file audio.
const std::vector< float > & spectrum() const
void set_sensitivity(float sensitivity)
unsigned int sample_rate() const
AudioMetrics metrics() const
float sensitivity() const
static constexpr int spectrum_bin_count()
void set_sample_rate(unsigned int sample_rate)
RAII owner for the RtAudio stream, analysis pipeline, and WAV recorder.
AudioRecorder & recorder()
AudioAnalyzer & analyzer()
unsigned int input_channels() const
bool open(const AudioStreamConfig &config)
static void list_devices()
bool is_recording() const
cv::Mat hardenedAlphaMask(const cv::Mat &image, const cv::Mat &mask, float blackPoint, float whitePoint)
cv::Mat isolateBody(const cv::Mat &image, const cv::Mat &mask, float blackPoint, float whitePoint)
constexpr int AC_FILTER_MAX
bool loadComputeProgramBinaryFromCache(const std::string &source, GLuint &program)
Restore a compute-only program binary keyed by source and GL driver.
bool saveComputeProgramBinaryToCache(const std::string &source, GLuint program)
Save a linked compute-only program to its stage-specific cache key.
constexpr std::size_t FFT_SIZE
constexpr std::uint32_t kShaderSelectionMagic
constexpr std::uint32_t kShaderSelectionMaxShaderName
constexpr std::uint32_t kShaderSelectionMaxPassCount
constexpr std::uint32_t kShaderSelectionMaxUniformName
constexpr std::uint32_t kShaderSelectionVersion
constexpr std::uint32_t kShaderSelectionMaxWatermarkText
constexpr std::uint32_t kShaderSelectionMaxReloadPath
constexpr const char * kShaderSelectionShmName
constexpr const char * kShaderSelectionSemaphoreName
constexpr std::uint32_t kShaderSelectionMaxGpuFilterCount
constexpr std::uint32_t kShaderSelectionMaxCustomUniforms
constexpr std::uint32_t kShaderSelectionMaxAudioFilePath
bool terminalSupportsColor()
void installHeadlessSignalHandlers()
CliColors makeCliColors()
std::atomic< bool > g_shutdown_requested
void printSection(Stream &out, const CliColors &c, std::string_view name, const std::vector< HelpEntry > &entries)
void printDetailedArguments(Stream &out)
void printKeyboardControls(Stream &out)
std::vector< int > shader_indices
A captured RGBA pixel buffer queued for the writer thread.
bool isHdr
True when pixels holds 16-bit PQ/HLG-encoded BT.2020 RGBA (8 bytes/pixel).
int width
Frame width in pixels.
bool isWebPSnapshot
True if this frame should be saved as a WebP snapshot.
bool isTiffSnapshot
True if this frame should be saved as a TIFF snapshot (16-bit HDR, 8-bit SDR).
int hdrTrc
AVColorTransferCharacteristic (PQ=16, HLG=18) when isHdr.
bool usesTimelineClock
Frame timing follows an external real-time clock.
std::vector< unsigned char > pixels
RGBA pixel data (SDR 8-bit RGBA, or HDR 16-bit RGBA when isHdr).
bool isRawSnapshot
True if this frame should be saved as a raw RGBA file.
bool isSnapshot
True if this frame should be saved as a PNG snapshot.
uint64_t timelineFrame
Clock position expressed in nominal video frames.
int height
Frame height in pixels.
Parsed command-line arguments for the ACMX2 application.
float cross_fade_duration
Crossfade duration in seconds when switching playlist shaders (default: 0.5).
float human_black
–black: mask black point (shadow crush threshold).
bool remove_broken
True when --remove-broken <path> was specified.
std::optional< cv::Size > sizev
std::vector< int > gpu_filter_indices
int autopilot_random_timeout
Upper bound (inclusive) for randomized autopilot interval.
FrameRotation frame_rotation
Optional input-frame rotation.
bool no_drop
In video mode, pace frame production to encoder throughput.
std::string watermark_text
User watermark text (–use-watermark). When non-empty, watermark is enabled.
unsigned int audio_channels
std::string edge_model
Dexined edge-detection ONNX model path (–edge). Empty when disabled.
bool flip_output
Vertical flip output frames when set (e.g., for HDR correction).
std::string remove_broken_path
Library path passed to --remove-broken.
std::string record_audio_file
bool audio_repeat
When true, restart file audio from the beginning at EOF.
std::string build_library_path
int gpu_frame_buffer_size
float audio_warm_rate
Startup warmup envelope rate (1/sec). 0.5 ~= 2s to full strength.
bool human_background_only
When true (with –human), shaders apply only to the background; person composited on top.
bool autopilot_random_interval
When true, randomize autopilot frame interval after each switch.
std::vector< std::string > shader_pass_files
std::tuple< int, std::string, int > slib
int watermark_g
Watermark green channel (0-255).
std::string playlist_file
float human_white
–white: mask white point (opacity saturation threshold).
int audio_buffers
Requested spectrum-history array depth.
EncodeOptions encode_opts
std::string onnx_model
Generic OnnxWrapper YAML config path (–onnx). Empty when disabled.
bool png_output
Video-file mode only: write PNG frames to a subdirectory instead of encoding video.
std::vector< int > shader_pass_list
int watermark_b
Watermark blue channel (0-255).
int autopilot_frames
Frames between random shader switches in autopilot mode (0 = disabled).
std::string human_model
PPHS human-segmentation ONNX model path (–human). Empty when disabled.
std::string midi_map_file
bool audio_trunc
When true, stop playback when file audio reaches the end.
int watermark_r
Watermark red channel (0-255), default magenta-pink.
bool display_filter
Display current shader/stack and GPU filter overlay in upper-left.
bool interface_shm
Enable interface shared-memory control channel (Qt launcher use).
int generate_interval
Save a PNG frame every N frames to a subdirectory (video or camera mode, 0 = disabled).
std::optional< cv::Size > csize
One shader's precompiled binary data for the on-disk cache.
uint64_t source_hash
FNV-1a-64 hash of the fragment source.
std::vector< char > binary_2d
GL program binary (2D vertex shader).
GLenum format_2d
GL binary format token for 2D.
GLenum format_3d
GL binary format token for 3D.
bool failed
True if this shader failed to compile; a passthrough program is substituted at load time to preserve ...
std::string shader_name
Stem of the .glsl filename.
std::vector< char > binary_3d
GL program binary (3D vertex shader).
Container for the full on-disk shader cache file.
static constexpr uint32_t CACHE_VERSION
Current format version.
bool load(const std::string &path)
Deserialise the shader cache from a binary file.
std::vector< ShaderCacheEntry > entries
static constexpr uint32_t CACHE_MAGIC
File magic: "SHDR".
bool save(const std::string &path) const
Serialise the entire shader cache to a binary file.
Cached uniform locations for a single compiled shader program.
std::vector< GLint > texture_array_loc
GLint slider_loc[4]
Locations of optional uniform float slider1..slider4;.
GLint texture_cache_loc[8]
GLint iChannelResolution[4]
GLint spectrum_history_head_loc
Newest physical array layer.
GLint spectrum_zero_loc
Current-frame spectrum0 alias.
GLint history_head_loc
Location of the array ring's oldest physical layer.
std::vector< GLint > custom_uniform_locs
GLint spectrum_history_size_loc
Runtime array depth.
GLint spectrum_loc
Live sampler1D spectrum.
GLint history_loc
Location of optional uniform sampler2DArray history;.
GLint compute_work_group_size[3]
GLint spectrum_history_loc
sampler1DArray spectrum_history.
GLint texture_array_base_loc
Base location of textures[0] for bulk glUniform1iv assignment.
std::vector< std::string > entries
ShaderManifestFormat format
std::vector< CustomUniform > custom_uniforms
std::int32_t audio_output_device
std::uint8_t normalized_time_enabled
std::uint8_t shader_pass_enabled
char selected_shader_name[kShaderSelectionMaxShaderName]
std::uint32_t shader_pass_count
std::int32_t gpu_filter_indices[kShaderSelectionMaxGpuFilterCount]
std::uint32_t gpu_filter_count
std::uint8_t repeat_enabled
char audio_file_path[kShaderSelectionMaxAudioFilePath]
char shader_pass_names[kShaderSelectionMaxPassCount][kShaderSelectionMaxShaderName]
std::uint8_t watermark_enabled
std::uint8_t audio_repeat
std::uint8_t gpu_buffer_size
std::uint8_t audio_pass_through
std::uint32_t audio_file_sequence
std::uint32_t reload_sequence
std::int32_t selected_index
char custom_uniform_names[kShaderSelectionMaxCustomUniforms][kShaderSelectionMaxUniformName]
std::uint8_t gpu_filter_enabled
std::uint8_t display_filter_enabled
char reload_shader_path[kShaderSelectionMaxReloadPath]
std::int32_t shader_pass_indices[kShaderSelectionMaxPassCount]
std::int32_t reload_shader_index
char watermark_text[kShaderSelectionMaxWatermarkText]
float custom_uniform_values[kShaderSelectionMaxCustomUniforms]
std::uint32_t custom_uniform_count
std::string_view description