3#include <mxvk/mxvk_png.hpp>
6#include <webp/encode.h>
26 std::lock_guard<std::mutex> lock(
mutex);
33 }
catch (
const std::exception &error) {
34 std::cerr <<
"acmxvk: could not start snapshot worker: "
35 << error.what() <<
'\n';
43 std::lock_guard<std::mutex> lock(
mutex);
54 std::lock_guard<std::mutex> lock(
mutex);
60 std::lock_guard<std::mutex> lock(
mutex);
61 jobs.push_back(std::move(job));
68 int width,
int height) {
69 if (!mxvk::SavePNG_RGBA(path.string().c_str(), rgba, width, height)) {
70 throw std::runtime_error(
"unable to write PNG frame: " +
76 const std::vector<std::uint8_t> &rgba,
77 std::uint32_t width, std::uint32_t height) {
78 if (width == 0U || height == 0U) {
79 throw std::runtime_error(
80 "invalid image dimensions for raw RGBA snapshot: " +
84 const std::uint64_t byte_count =
85 static_cast<std::uint64_t
>(width) *
86 static_cast<std::uint64_t
>(height) * 4U;
87 if (byte_count > rgba.size() ||
88 byte_count >
static_cast<std::uint64_t
>(
89 std::numeric_limits<std::streamsize>::max())) {
90 throw std::runtime_error(
91 "invalid pixel buffer for raw RGBA snapshot: " + path.string());
94 std::ofstream output(path, std::ios::binary);
96 throw std::runtime_error(
"unable to open raw RGBA snapshot: " +
99 output.write(
reinterpret_cast<const char *
>(rgba.data()),
100 static_cast<std::streamsize
>(byte_count));
102 throw std::runtime_error(
"unable to write raw RGBA snapshot: " +
108 const fs::path &path,
const std::vector<std::uint16_t> &rgba,
109 std::uint32_t width, std::uint32_t height) {
110 const std::uint64_t sample_count =
111 static_cast<std::uint64_t
>(width) * height * 4U;
112 const std::uint64_t byte_count = sample_count *
sizeof(std::uint16_t);
113 if (width == 0U || height == 0U || sample_count > rgba.size() ||
114 byte_count >
static_cast<std::uint64_t
>(
115 std::numeric_limits<std::streamsize>::max())) {
116 throw std::runtime_error(
117 "invalid pixel buffer for raw RGBA16 snapshot: " +
120 std::ofstream output(path, std::ios::binary);
122 throw std::runtime_error(
"unable to open raw RGBA16 snapshot: " +
125 output.write(
reinterpret_cast<const char *
>(rgba.data()),
126 static_cast<std::streamsize
>(byte_count));
128 throw std::runtime_error(
"unable to write raw RGBA16 snapshot: " +
133#ifdef ACMXVK_WITH_WEBP
134 void SnapshotWriter::saveWebP(
const fs::path &path,
135 const std::uint8_t *rgba,
int width,
137 if (rgba ==
nullptr || width <= 0 || height <= 0 ||
138 width > std::numeric_limits<int>::max() / 4) {
139 throw std::runtime_error(
140 "invalid image dimensions for WebP snapshot: " + path.string());
143 std::uint8_t *encoded_pixels =
nullptr;
144 const std::size_t encoded_size = WebPEncodeLosslessRGBA(
145 rgba, width, height, width * 4, &encoded_pixels);
146 const std::unique_ptr<std::uint8_t,
decltype(&WebPFree)> encoded_data(
147 encoded_pixels, &WebPFree);
148 if (encoded_size == 0 || encoded_data ==
nullptr) {
149 throw std::runtime_error(
"unable to encode WebP snapshot: " +
153 std::ofstream output(path, std::ios::binary);
155 throw std::runtime_error(
"unable to open WebP snapshot: " +
158 output.write(
reinterpret_cast<const char *
>(encoded_data.get()),
159 static_cast<std::streamsize
>(encoded_size));
161 throw std::runtime_error(
"unable to write WebP snapshot: " +
167#ifdef ACMXVK_WITH_TIFF
168 void SnapshotWriter::saveTiff(
const fs::path &path,
169 const std::uint8_t *rgba,
int width,
171 if (rgba ==
nullptr || width <= 0 || height <= 0 ||
172 width > std::numeric_limits<int>::max() / 4) {
173 throw std::runtime_error(
174 "invalid image dimensions for TIFF snapshot: " + path.string());
177 const std::unique_ptr<TIFF,
decltype(&TIFFClose)> output(
178 TIFFOpen(path.string().c_str(),
"w"), &TIFFClose);
179 if (output ==
nullptr) {
180 throw std::runtime_error(
"unable to open TIFF snapshot: " +
184 const std::uint16_t extra_sample = EXTRASAMPLE_UNASSALPHA;
185 const bool configured =
186 TIFFSetField(output.get(), TIFFTAG_IMAGEWIDTH,
187 static_cast<std::uint32_t
>(width)) != 0 &&
188 TIFFSetField(output.get(), TIFFTAG_IMAGELENGTH,
189 static_cast<std::uint32_t
>(height)) != 0 &&
190 TIFFSetField(output.get(), TIFFTAG_SAMPLESPERPIXEL, 4) != 0 &&
191 TIFFSetField(output.get(), TIFFTAG_BITSPERSAMPLE, 8) != 0 &&
192 TIFFSetField(output.get(), TIFFTAG_ORIENTATION,
193 ORIENTATION_TOPLEFT) != 0 &&
194 TIFFSetField(output.get(), TIFFTAG_PLANARCONFIG,
195 PLANARCONFIG_CONTIG) != 0 &&
196 TIFFSetField(output.get(), TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB) !=
198 TIFFSetField(output.get(), TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_UINT) !=
200 TIFFSetField(output.get(), TIFFTAG_COMPRESSION, COMPRESSION_LZW) !=
202 TIFFSetField(output.get(), TIFFTAG_ROWSPERSTRIP,
203 TIFFDefaultStripSize(output.get(), 0)) != 0 &&
204 TIFFSetField(output.get(), TIFFTAG_EXTRASAMPLES, 1,
205 &extra_sample) != 0 &&
206 TIFFSetField(output.get(), TIFFTAG_IMAGEDESCRIPTION,
207 "ACMXVK processed snapshot: 8-bit RGBA TIFF") != 0;
209 throw std::runtime_error(
"unable to configure TIFF snapshot: " +
213 const std::size_t row_bytes =
static_cast<std::size_t
>(width) * 4U;
214 for (
int row = 0; row < height; ++row) {
215 auto *row_pixels =
const_cast<std::uint8_t *
>(
216 rgba +
static_cast<std::size_t
>(row) * row_bytes);
217 if (TIFFWriteScanline(output.get(), row_pixels,
218 static_cast<std::uint32_t
>(row), 0) < 0) {
219 throw std::runtime_error(
"unable to write TIFF snapshot: " +
225 void SnapshotWriter::saveTiff16(
const fs::path &path,
226 const std::uint16_t *rgba,
int width,
228 if (rgba ==
nullptr || width <= 0 || height <= 0 ||
229 width > std::numeric_limits<int>::max() / 8) {
230 throw std::runtime_error(
231 "invalid image dimensions for 16-bit TIFF snapshot: " +
234 const std::unique_ptr<TIFF,
decltype(&TIFFClose)> output(
235 TIFFOpen(path.string().c_str(),
"w"), &TIFFClose);
236 if (output ==
nullptr) {
237 throw std::runtime_error(
"unable to open TIFF snapshot: " +
240 const std::uint16_t extra_sample = EXTRASAMPLE_UNASSALPHA;
241 const bool configured =
242 TIFFSetField(output.get(), TIFFTAG_IMAGEWIDTH,
243 static_cast<std::uint32_t
>(width)) != 0 &&
244 TIFFSetField(output.get(), TIFFTAG_IMAGELENGTH,
245 static_cast<std::uint32_t
>(height)) != 0 &&
246 TIFFSetField(output.get(), TIFFTAG_SAMPLESPERPIXEL, 4) != 0 &&
247 TIFFSetField(output.get(), TIFFTAG_BITSPERSAMPLE, 16) != 0 &&
248 TIFFSetField(output.get(), TIFFTAG_ORIENTATION,
249 ORIENTATION_TOPLEFT) != 0 &&
250 TIFFSetField(output.get(), TIFFTAG_PLANARCONFIG,
251 PLANARCONFIG_CONTIG) != 0 &&
252 TIFFSetField(output.get(), TIFFTAG_PHOTOMETRIC,
253 PHOTOMETRIC_RGB) != 0 &&
254 TIFFSetField(output.get(), TIFFTAG_SAMPLEFORMAT,
255 SAMPLEFORMAT_UINT) != 0 &&
256 TIFFSetField(output.get(), TIFFTAG_COMPRESSION, COMPRESSION_LZW) !=
258 TIFFSetField(output.get(), TIFFTAG_ROWSPERSTRIP,
259 TIFFDefaultStripSize(output.get(), 0)) != 0 &&
260 TIFFSetField(output.get(), TIFFTAG_EXTRASAMPLES, 1,
261 &extra_sample) != 0 &&
262 TIFFSetField(output.get(), TIFFTAG_IMAGEDESCRIPTION,
263 "ACMXVK HDR snapshot: 16-bit RGBA") != 0;
265 throw std::runtime_error(
"unable to configure TIFF snapshot: " +
268 const std::size_t row_samples =
static_cast<std::size_t
>(width) * 4U;
269 for (
int row = 0; row < height; ++row) {
270 auto *row_pixels =
const_cast<std::uint16_t *
>(
271 rgba +
static_cast<std::size_t
>(row) * row_samples);
272 if (TIFFWriteScanline(output.get(), row_pixels,
273 static_cast<std::uint32_t
>(row), 0) < 0) {
274 throw std::runtime_error(
"unable to write TIFF snapshot: " +
299 std::unique_lock<std::mutex> lock(
mutex);
305 job = std::move(
jobs.front());
311 if (!job.
rgba16.empty()) {
317#ifdef ACMXVK_WITH_TIFF
318 if (!job.
rgba16.empty()) {
320 static_cast<int>(job.
width),
321 static_cast<int>(job.
height));
324 static_cast<int>(job.
width),
325 static_cast<int>(job.
height));
328 throw std::runtime_error(
329 "TIFF snapshot support is not compiled in");
332#ifdef ACMXVK_WITH_WEBP
334 static_cast<int>(job.
width),
335 static_cast<int>(job.
height));
337 throw std::runtime_error(
338 "WebP snapshot support is not compiled in");
342 static_cast<int>(job.
width),
343 static_cast<int>(job.
height));
345 std::ostringstream message;
347 <<
" snapshot: " << job.
path.string() <<
'\n';
348 std::cout << message.str();
349 }
catch (
const std::exception &error) {
350 std::ostringstream message;
351 message <<
"acmxvk: snapshot failed: " << error.what() <<
'\n';
352 std::cerr << message.str();
355 <<
"acmxvk: snapshot failed with an unknown error\n";
358 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)
static void savePng(const fs::path &path, std::uint8_t *rgba, int width, int 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)
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