Skip to content

Commit 3071126

Browse files
authored
Merge pull request #427 from Tom94/small-vector
feat(channel): small buffer optimization for MultiChannelView
2 parents 577e8e3 + 9c2c37b commit 3071126

18 files changed

Lines changed: 74 additions & 71 deletions

.gitmodules

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,7 @@
106106
path = dependencies/libtiff
107107
url = https://github.com/Tom94/libtiff
108108
shallow = true
109+
[submodule "dependencies/small_vector"]
110+
path = dependencies/small_vector
111+
url = https://github.com/gharveymn/small_vector
112+
shallow = true

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ target_include_directories(tev PRIVATE
243243
${CMAKE_CURRENT_SOURCE_DIR}/dependencies
244244
${CMAKE_CURRENT_SOURCE_DIR}/dependencies/args
245245
${CMAKE_CURRENT_SOURCE_DIR}/dependencies/qoi
246+
${CMAKE_CURRENT_SOURCE_DIR}/dependencies/small_vector/source/include
246247
${CMAKE_CURRENT_SOURCE_DIR}/dependencies/stb
247248
${CMAKE_CURRENT_SOURCE_DIR}/dependencies/tinylogger
248249
${CMAKE_CURRENT_SOURCE_DIR}/dependencies/utfcpp/source

dependencies/small_vector

Submodule small_vector added at b19a9c4

include/tev/Channel.h

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,15 @@
2222
#include <tev/Common.h>
2323
#include <tev/Task.h>
2424

25-
#include <nanogui/vector.h>
26-
25+
#include <gch/small_vector.hpp>
2726
#include <half.h>
27+
#include <nanogui/vector.h>
2828

2929
#include <memory>
3030
#include <ranges>
3131
#include <span>
3232
#include <string>
3333
#include <type_traits>
34-
#include <vector>
3534

3635
namespace tev {
3736

@@ -62,7 +61,7 @@ template <typename T> class ChannelView {
6261
public:
6362
ChannelView(const ChannelView<std::remove_const_t<T>>& other)
6463
requires(!std::is_same_v<T, std::remove_const_t<T>>)
65-
: ChannelView(other.data(), other.dataStride(), other.dataOffset(), other.size()) {}
64+
: ChannelView(other.data(), other.dataStride(), 0, other.size()) {}
6665

6766
ChannelView(const ChannelView&) = default;
6867
ChannelView& operator=(const ChannelView&) = default;
@@ -71,14 +70,14 @@ template <typename T> class ChannelView {
7170
ChannelView& operator=(ChannelView&&) = default;
7271

7372
ChannelView(T* data, size_t dataStride, size_t dataOffset, nanogui::Vector2i size) :
74-
mData{data}, mDataOffset{dataOffset}, mDataStride{dataStride}, mSize{size} {}
73+
mData{data + dataOffset}, mDataStride{dataStride}, mSize{size} {}
7574

7675
std::conditional_t<std::is_same_v<T, float>, float&, float> operator[](size_t i) const & {
7776
if constexpr (std::is_integral_v<T>) {
78-
const auto v = mData[mDataOffset + i * mDataStride];
77+
const auto v = mData[i * mDataStride];
7978
return (float)v / (float)std::numeric_limits<T>::max();
8079
} else {
81-
return mData[mDataOffset + i * mDataStride];
80+
return mData[i * mDataStride];
8281
}
8382
}
8483

@@ -90,7 +89,7 @@ template <typename T> class ChannelView {
9089
float operator[](int x, int y) const && { return this->operator[](x, y); }
9190

9291
void setAt(size_t i, float value) const {
93-
T& val = mData[mDataOffset + i * mDataStride];
92+
T& val = mData[i * mDataStride];
9493
if constexpr (std::is_integral_v<T>) {
9594
if constexpr (std::is_signed_v<T>) {
9695
val = (T)(std::clamp(value, -1.0f, 1.0f) * (float)std::numeric_limits<T>::max() + copysignf(0.5f, value));
@@ -108,12 +107,10 @@ template <typename T> class ChannelView {
108107

109108
T* data() const & { return mData; }
110109

111-
size_t dataOffset() const { return mDataOffset; }
112110
size_t dataStride() const { return mDataStride; }
113111

114112
private:
115113
T* mData = nullptr;
116-
size_t mDataOffset = 0;
117114
size_t mDataStride = 1;
118115
nanogui::Vector2i mSize = {0};
119116
};
@@ -345,6 +342,9 @@ class Channel {
345342
size_t mDataStride;
346343
};
347344

345+
template <typename T> using SmallRgbaVector = gch::small_vector<T, 4>; // Up to 4 channels should be stored on the stack
346+
inline constexpr detail::to_vector_fn<SmallRgbaVector> toSmallRgbaVector{};
347+
348348
template <typename T> class MultiChannelView {
349349
public:
350350
MultiChannelView() = delete;
@@ -354,7 +354,10 @@ template <typename T> class MultiChannelView {
354354
numChannels = dataStride;
355355
}
356356

357-
mSize = size;
357+
if (numChannels == 0) {
358+
throw std::runtime_error{"MultiChannelView(ptr) must have at least one channel."};
359+
}
360+
358361
for (size_t c = 0; c < numChannels; ++c) {
359362
mChannelViews.emplace_back(data, dataStride, c, size);
360363
}
@@ -367,20 +370,20 @@ template <typename T> class MultiChannelView {
367370

368371
MultiChannelView(std::span<Channel> channels)
369372
requires(!std::is_const_v<T>)
370-
: MultiChannelView{channels | std::views::transform([](Channel& c) { return c.view<T>(); }) | to_vector} {}
373+
: MultiChannelView{channels | std::views::transform([](Channel& c) { return c.view<T>(); }) | toSmallRgbaVector} {}
371374

372375
MultiChannelView(std::span<const Channel> channels)
373376
requires(std::is_const_v<T>)
374-
: MultiChannelView{channels | std::views::transform([](const Channel& c) { return c.view<T>(); }) | to_vector} {}
377+
: MultiChannelView{channels | std::views::transform([](const Channel& c) { return c.view<T>(); }) | toSmallRgbaVector} {}
375378

376379
MultiChannelView(std::span<const ChannelView<T>> views) : mChannelViews{views.begin(), views.end()} {
377380
if (mChannelViews.empty()) {
378-
throw std::runtime_error{"MultiChannelView must have at least one channel."};
381+
throw std::runtime_error{"MultiChannelView(span) must have at least one channel."};
379382
}
380383

381-
mSize = mChannelViews.front().size();
384+
const auto s = mChannelViews.front().size();
382385
for (const auto& channel : mChannelViews) {
383-
if (channel.size() != mSize) {
386+
if (channel.size() != s) {
384387
throw std::runtime_error{"All channels in a MultiChannelView must have the same size."};
385388
}
386389
}
@@ -392,8 +395,8 @@ template <typename T> class MultiChannelView {
392395
decltype(auto) operator[](int c, size_t i) const & { return mChannelViews[channelIdx(c)][i]; }
393396
decltype(auto) operator[](int c, int x, int y) const & { return mChannelViews[channelIdx(c)][x, y]; }
394397

395-
float operator[](int c, size_t i) const && { return mChannelViews[channelIdx(c)][i]; }
396-
float operator[](int c, int x, int y) const && { return mChannelViews[channelIdx(c)][x, y]; }
398+
auto operator[](int c, size_t i) const && { return mChannelViews[channelIdx(c)][i]; }
399+
auto operator[](int c, int x, int y) const && { return mChannelViews[channelIdx(c)][x, y]; }
397400

398401
void setAt(int c, size_t i, float value) const { mChannelViews[channelIdx(c)].setAt(i, value); }
399402
void setAt(int c, int x, int y, float value) const { mChannelViews[channelIdx(c)].setAt(x, y, value); }
@@ -402,7 +405,8 @@ template <typename T> class MultiChannelView {
402405
const auto& front = mChannelViews.front();
403406
for (size_t i = 0; i < mChannelViews.size(); ++i) {
404407
const auto& channel = mChannelViews[i];
405-
if (channel.data() != front.data() || channel.dataOffset() != i || channel.dataStride() != front.dataStride()) {
408+
const auto offset = channel.data() - front.data();
409+
if (channel.data() != front.data() || offset != (ptrdiff_t)i || channel.dataStride() != front.dataStride()) {
406410
return std::nullopt;
407411
}
408412
}
@@ -420,12 +424,11 @@ template <typename T> class MultiChannelView {
420424
return mChannelViews.front().data();
421425
}
422426

423-
nanogui::Vector2i size() const { return mSize; }
427+
nanogui::Vector2i size() const { return mChannelViews.front().size(); }
424428
size_t nChannels() const { return mChannelViews.size(); }
425429

426430
private:
427-
std::vector<ChannelView<T>> mChannelViews;
428-
nanogui::Vector2i mSize = {0};
431+
SmallRgbaVector<ChannelView<T>> mChannelViews;
429432
};
430433

431434
} // namespace tev

include/tev/Common.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -257,18 +257,18 @@ namespace fs = std::filesystem;
257257
// Same for fixed_chunks once std::ranges::views::chunk becomes available.
258258
namespace detail {
259259

260-
struct to_vector_fn {
260+
template <template <typename...> class Vector> struct to_vector_fn {
261261
template <std::ranges::range R> friend constexpr auto operator|(R&& r, to_vector_fn) {
262262
using value_type = std::ranges::range_value_t<R>;
263263
if constexpr (std::ranges::sized_range<R>) {
264-
std::vector<value_type> v;
264+
Vector<value_type> v;
265265
v.reserve(std::ranges::size(r));
266266
for (auto&& e : r) {
267267
v.emplace_back(static_cast<decltype(e)&&>(e));
268268
}
269269
return v;
270270
} else {
271-
return std::vector<value_type>(std::ranges::begin(r), std::ranges::end(r));
271+
return Vector<value_type>(std::ranges::begin(r), std::ranges::end(r));
272272
}
273273
}
274274
};
@@ -282,7 +282,7 @@ template <size_t N> struct fixed_chunks_fn {
282282

283283
} // namespace detail
284284

285-
inline constexpr detail::to_vector_fn to_vector{};
285+
inline constexpr detail::to_vector_fn<std::vector> toVector{};
286286
template <std::size_t N> inline constexpr detail::fixed_chunks_fn<N> fixed_chunks{};
287287

288288
// Helper for std::visit on multiple lambdas

include/tev/imageio/ImageLoader.h

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include <nanogui/vector.h>
2828

2929
#include <istream>
30+
#include <ranges>
3031
#include <stdexcept>
3132
#include <string>
3233
#include <type_traits>
@@ -72,9 +73,9 @@ Task<void> yCbCrToRgb(MultiChannelView<float> data, int priority, nanogui::Vecto
7273
);
7374
}
7475

75-
template <typename T, bool SRGB_TO_LINEAR = false, bool MULTIPLY_ALPHA = false>
76+
template <bool SRGB_TO_LINEAR = false, bool MULTIPLY_ALPHA = false, std::ranges::random_access_range T>
7677
Task<void> toFloat32(
77-
std::span<const T> imageData,
78+
T&& imageData,
7879
size_t numSamplesPerPixelIn,
7980
MultiChannelView<float> floatData,
8081
bool hasAlpha,
@@ -84,9 +85,10 @@ Task<void> toFloat32(
8485
// 0 defaults to numSamplesPerPixelIn * size.x()
8586
size_t numSamplesPerRowIn = 0
8687
) {
87-
if constexpr (std::is_integral_v<T>) {
88+
using value_t = typename std::remove_cvref_t<T>::value_type;
89+
if constexpr (std::is_integral_v<value_t>) {
8890
if (scale == 0.0f) {
89-
scale = 1.0f / (((size_t)1 << (sizeof(T) * 8)) - 1);
91+
scale = 1.0f / (((size_t)1 << (sizeof(value_t) * 8)) - 1);
9092
}
9193
} else {
9294
if (scale == 0.0f) {

src/HelpWindow.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ HelpWindow::HelpWindow(Widget* parent, weak_ptr<Ipc> weakIpc, function<void()> c
262262
addLibrary(about, "OpenEXR", "EXR image format library");
263263
addLibrary(about, "OpenJPEG", "JPEG 2000 image format library");
264264
addLibrary(about, "qoi", "QOI image format library");
265+
addLibrary(about, "small_vector", "Vector with small buffer optimization");
265266
addLibrary(about, "stb_image(_write)", "Single-header library for loading and writing images");
266267
addLibrary(about, "tinylogger", "Minimal pretty-logging library");
267268
addLibrary(about, "UTF8-CPP", "Lightweight UTF-8 string manipulation library");

src/Image.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1109,7 +1109,7 @@ Task<vector<Channel>> Image::getHdrImageData(shared_ptr<Image> reference, string
11091109
result.emplace_back(toUpper(Channel::tail(channelNames[i])), size, EPixelFormat::F32, EPixelFormat::F32);
11101110
}
11111111

1112-
const auto views = result | views::transform([](Channel& c) { return c.view<float>(); }) | to_vector;
1112+
const auto views = result | views::transform([](Channel& c) { return c.view<float>(); }) | toVector;
11131113
const auto channels = this->channels(channelNames);
11141114
if (!reference) {
11151115
co_await ThreadPool::global().parallelFor(

src/ImageCanvas.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -809,7 +809,7 @@ Task<shared_ptr<CanvasStatistics>> ImageCanvas::computeCanvasStatistics(
809809
});
810810

811811
auto flattened = co_await image->getHdrImageData(reference, requestedChannelGroup, metric, priority);
812-
const auto views = flattened | views::transform([](Channel& c) { return c.view<float>(); }) | to_vector;
812+
const auto views = flattened | views::transform([](Channel& c) { return c.view<float>(); }) | toVector;
813813

814814
const ChannelView<float>* alphaChannel = nullptr;
815815

src/imageio/DdsImageLoader.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ Task<vector<ImageData>> DdsImageLoader::load(istream& iStream, const fs::path&,
258258
} else {
259259
// Ideally, we'd be able to assume that only *_SRGB format images were in sRGB space, and only they need to converted to linear.
260260
// However, RGB(A) DDS images tend to be in sRGB space, even those not explicitly stored in an *_SRGB format.
261-
co_await toFloat32<float, true>(s, numChannels, outView, hasAlpha, priority);
261+
co_await toFloat32<true>(s, numChannels, outView, hasAlpha, priority);
262262
}
263263

264264
co_return result;

0 commit comments

Comments
 (0)