3#include <mxvk/mxvk_png.hpp>
6#include <webp/encode.h>
24 std::lock_guard<std::mutex> lock(
mutex);
31 }
catch (
const std::exception &error) {
32 std::cerr <<
"acmxvk: could not start snapshot worker: " << error.what() <<
'\n';
40 std::lock_guard<std::mutex> lock(
mutex);
51 std::lock_guard<std::mutex> lock(
mutex);
57 std::lock_guard<std::mutex> lock(
mutex);
58 jobs.push_back(std::move(job));
65 if (!mxvk::SavePNG_RGBA(path.string().c_str(), rgba, width, height, png_level)) {
66 throw std::runtime_error(
"unable to write PNG frame: " + path.string());
70 void SnapshotWriter::saveRaw(
const fs::path &path,
const std::vector<std::uint8_t> &rgba, std::uint32_t width, std::uint32_t height) {
71 if (width == 0U || height == 0U) {
72 throw std::runtime_error(
"invalid image dimensions for raw RGBA snapshot: " + path.string());
75 const std::uint64_t byte_count =
static_cast<std::uint64_t
>(width) *
static_cast<std::uint64_t
>(height) * 4U;
76 if (byte_count > rgba.size() || byte_count >
static_cast<std::uint64_t
>(std::numeric_limits<std::streamsize>::max())) {
77 throw std::runtime_error(
"invalid pixel buffer for raw RGBA snapshot: " + path.string());
80 std::ofstream output(path, std::ios::binary);
82 throw std::runtime_error(
"unable to open raw RGBA snapshot: " + path.string());
84 output.write(
reinterpret_cast<const char *
>(rgba.data()),
static_cast<std::streamsize
>(byte_count));
86 throw std::runtime_error(
"unable to write raw RGBA snapshot: " + path.string());
90 void SnapshotWriter::saveRaw16(
const fs::path &path,
const std::vector<std::uint16_t> &rgba, std::uint32_t width, std::uint32_t height) {
91 const std::uint64_t sample_count =
static_cast<std::uint64_t
>(width) * height * 4U;
92 const std::uint64_t byte_count = sample_count *
sizeof(std::uint16_t);
93 if (width == 0U || height == 0U || sample_count > rgba.size() || byte_count >
static_cast<std::uint64_t
>(std::numeric_limits<std::streamsize>::max())) {
94 throw std::runtime_error(
"invalid pixel buffer for raw RGBA16 snapshot: " + path.string());
96 std::ofstream output(path, std::ios::binary);
98 throw std::runtime_error(
"unable to open raw RGBA16 snapshot: " + path.string());
100 output.write(
reinterpret_cast<const char *
>(rgba.data()),
static_cast<std::streamsize
>(byte_count));
102 throw std::runtime_error(
"unable to write raw RGBA16 snapshot: " + path.string());
106#ifdef ACMXVK_WITH_WEBP
107 void SnapshotWriter::saveWebP(
const fs::path &path,
const std::uint8_t *rgba,
int width,
int height) {
108 if (rgba ==
nullptr || width <= 0 || height <= 0 || width > std::numeric_limits<int>::max() / 4) {
109 throw std::runtime_error(
"invalid image dimensions for WebP snapshot: " + path.string());
112 std::uint8_t *encoded_pixels =
nullptr;
113 const std::size_t encoded_size = WebPEncodeLosslessRGBA(rgba, width, height, width * 4, &encoded_pixels);
114 const std::unique_ptr<std::uint8_t,
decltype(&WebPFree)> encoded_data(encoded_pixels, &WebPFree);
115 if (encoded_size == 0 || encoded_data ==
nullptr) {
116 throw std::runtime_error(
"unable to encode WebP snapshot: " + path.string());
119 std::ofstream output(path, std::ios::binary);
121 throw std::runtime_error(
"unable to open WebP snapshot: " + path.string());
123 output.write(
reinterpret_cast<const char *
>(encoded_data.get()),
static_cast<std::streamsize
>(encoded_size));
125 throw std::runtime_error(
"unable to write WebP snapshot: " + path.string());
130#ifdef ACMXVK_WITH_TIFF
131 void SnapshotWriter::saveTiff(
const fs::path &path,
const std::uint8_t *rgba,
int width,
int height) {
132 if (rgba ==
nullptr || width <= 0 || height <= 0 || width > std::numeric_limits<int>::max() / 4) {
133 throw std::runtime_error(
"invalid image dimensions for TIFF snapshot: " + path.string());
136 const std::unique_ptr<TIFF,
decltype(&TIFFClose)> output(TIFFOpen(path.string().c_str(),
"w"), &TIFFClose);
137 if (output ==
nullptr) {
138 throw std::runtime_error(
"unable to open TIFF snapshot: " + path.string());
141 const std::uint16_t extra_sample = EXTRASAMPLE_UNASSALPHA;
142 const bool configured = TIFFSetField(output.get(), TIFFTAG_IMAGEWIDTH,
static_cast<std::uint32_t
>(width)) != 0 && TIFFSetField(output.get(), TIFFTAG_IMAGELENGTH,
static_cast<std::uint32_t
>(height)) != 0 && TIFFSetField(output.get(), TIFFTAG_SAMPLESPERPIXEL, 4) != 0 && TIFFSetField(output.get(), TIFFTAG_BITSPERSAMPLE, 8) != 0 && TIFFSetField(output.get(), TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT) != 0 && TIFFSetField(output.get(), TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG) != 0 && TIFFSetField(output.get(), TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB) != 0 && TIFFSetField(output.get(), TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_UINT) != 0 && TIFFSetField(output.get(), TIFFTAG_COMPRESSION, COMPRESSION_LZW) != 0 && TIFFSetField(output.get(), TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(output.get(), 0)) != 0 && TIFFSetField(output.get(), TIFFTAG_EXTRASAMPLES, 1, &extra_sample) != 0 && TIFFSetField(output.get(), TIFFTAG_IMAGEDESCRIPTION,
"ACMXVK processed snapshot: 8-bit RGBA TIFF") != 0;
144 throw std::runtime_error(
"unable to configure TIFF snapshot: " + path.string());
147 const std::size_t row_bytes =
static_cast<std::size_t
>(width) * 4U;
148 for (
int row = 0; row < height; ++row) {
149 auto *row_pixels =
const_cast<std::uint8_t *
>(rgba +
static_cast<std::size_t
>(row) * row_bytes);
150 if (TIFFWriteScanline(output.get(), row_pixels,
static_cast<std::uint32_t
>(row), 0) < 0) {
151 throw std::runtime_error(
"unable to write TIFF snapshot: " + path.string());
156 void SnapshotWriter::saveTiff16(
const fs::path &path,
const std::uint16_t *rgba,
int width,
int height) {
157 if (rgba ==
nullptr || width <= 0 || height <= 0 || width > std::numeric_limits<int>::max() / 8) {
158 throw std::runtime_error(
"invalid image dimensions for 16-bit TIFF snapshot: " + path.string());
160 const std::unique_ptr<TIFF,
decltype(&TIFFClose)> output(TIFFOpen(path.string().c_str(),
"w"), &TIFFClose);
161 if (output ==
nullptr) {
162 throw std::runtime_error(
"unable to open TIFF snapshot: " + path.string());
164 const std::uint16_t extra_sample = EXTRASAMPLE_UNASSALPHA;
165 const bool configured = TIFFSetField(output.get(), TIFFTAG_IMAGEWIDTH,
static_cast<std::uint32_t
>(width)) != 0 && TIFFSetField(output.get(), TIFFTAG_IMAGELENGTH,
static_cast<std::uint32_t
>(height)) != 0 && TIFFSetField(output.get(), TIFFTAG_SAMPLESPERPIXEL, 4) != 0 && TIFFSetField(output.get(), TIFFTAG_BITSPERSAMPLE, 16) != 0 && TIFFSetField(output.get(), TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT) != 0 && TIFFSetField(output.get(), TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG) != 0 && TIFFSetField(output.get(), TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB) != 0 && TIFFSetField(output.get(), TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_UINT) != 0 && TIFFSetField(output.get(), TIFFTAG_COMPRESSION, COMPRESSION_LZW) != 0 && TIFFSetField(output.get(), TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(output.get(), 0)) != 0 && TIFFSetField(output.get(), TIFFTAG_EXTRASAMPLES, 1, &extra_sample) != 0 && TIFFSetField(output.get(), TIFFTAG_IMAGEDESCRIPTION,
"ACMXVK HDR snapshot: 16-bit RGBA") != 0;
167 throw std::runtime_error(
"unable to configure TIFF snapshot: " + path.string());
169 const std::size_t row_samples =
static_cast<std::size_t
>(width) * 4U;
170 for (
int row = 0; row < height; ++row) {
171 auto *row_pixels =
const_cast<std::uint16_t *
>(rgba +
static_cast<std::size_t
>(row) * row_samples);
172 if (TIFFWriteScanline(output.get(), row_pixels,
static_cast<std::uint32_t
>(row), 0) < 0) {
173 throw std::runtime_error(
"unable to write TIFF snapshot: " + path.string());
197 std::unique_lock<std::mutex> lock(
mutex);
202 job = std::move(
jobs.front());
208 if (!job.
rgba16.empty()) {
214#ifdef ACMXVK_WITH_TIFF
215 if (!job.
rgba16.empty()) {
218 saveTiff(job.
path, job.
rgba.data(),
static_cast<int>(job.
width),
static_cast<int>(job.
height));
221 throw std::runtime_error(
"TIFF snapshot support is not compiled in");
224#ifdef ACMXVK_WITH_WEBP
225 saveWebP(job.
path, job.
rgba.data(),
static_cast<int>(job.
width),
static_cast<int>(job.
height));
227 throw std::runtime_error(
"WebP snapshot support is not compiled in");
232 std::ostringstream message;
233 message <<
"acmxvk: took " <<
formatName(job.
format) <<
" snapshot: " << job.
path.string() <<
'\n';
234 std::cout << message.str();
235 }
catch (
const std::exception &error) {
236 std::ostringstream message;
237 message <<
"acmxvk: snapshot failed: " << error.what() <<
'\n';
238 std::cerr << message.str();
240 std::cerr <<
"acmxvk: snapshot failed with an unknown error\n";
243 std::lock_guard<std::mutex> lock(
mutex);
std::condition_variable condition
static void saveRaw16(const fs::path &path, const std::vector< std::uint16_t > &rgba, std::uint32_t width, std::uint32_t height)
std::deque< SnapshotJob > jobs
static std::string_view formatName(SnapshotFormat format) noexcept
static void saveRaw(const fs::path &path, const std::vector< std::uint8_t > &rgba, std::uint32_t width, std::uint32_t height)
static void savePng(const fs::path &path, std::uint8_t *rgba, int width, int height, int png_level)
std::size_t jobs_in_flight
static constexpr std::size_t QUEUE_CAPACITY
void workerLoop() noexcept
void enqueue(SnapshotJob job)
std::vector< std::uint16_t > rgba16
std::vector< std::uint8_t > rgba