Инициализация std::vector с повторяющимся узором

В данный момент я работаю с OpenGL, создавая "текстурный кеш", который обрабатывает загрузку изображений и буферизует их с помощью OpenGL. Если файл изображения не может быть загружен, ему необходимо вернуться к текстуре по умолчанию, которую я жестко запрограммировал в конструкторе.

Что мне в основном нужно сделать, это создать текстуру однородного цвета. Это не так уж сложно, это просто массив пикселей размером * Цветные каналы.

В настоящее время я использую std::vector для хранения исходных данных, прежде чем загружать их OpenGL. У меня проблема в том, что я не могу найти никакой информации о наилучшем способе инициализации вектора с повторяющимся узором.

Первый способ, который пришёл мне в голову, - это использовать цикл.

std::vector<unsigned char> blue_texture;
for (int iii = 0; iii < width * height; iii++)
{
    blue_texture.push_back(0);
    blue_texture.push_back(0);
    blue_texture.push_back(255);
    blue_texture.push_back(255);
}

Тем не менее, это кажется неэффективным, так как вектор должен будет изменить свой размер много раз. Даже если я сначала зарезервирую пространство и выполню цикл, он все равно неэффективен, поскольку содержимое будет обнулено перед циклом, что означает две записи для каждого беззнакового символа.

В настоящее время я использую следующий метод:

struct colour {unsigned char r; unsigned char g; unsigned char b; unsigned char a;};
colour blue = {0, 0, 255, 255};
std::vector<colour> texture((width * height), blue);

Затем я извлекаю данные, используя:

reinterpret_cast<unsigned char*>(texture.data());

Есть ли лучший способ, чем этот? Я новичок в C/C++ и, честно говоря, использование указателей пугает меня.

5 ответов

Решение

По моему мнению, ваше решение петли - верный путь. Чтобы сделать это эффективным путем удаления повторных вызовов realloc, используйте blue_texture.reserve(width * height * 4)

Резервный вызов увеличит распределение, то есть емкость до этого размера, без заполнения нуля. (Обратите внимание, что операционная система может по-прежнему обнулять ее, если, например, она извлекает память из mmap.) Она не меняет размер вектора, поэтому push_back и friends по-прежнему работают одинаково.

Самый эффективный способ - это способ, который работает меньше всего.

К сожалению, push_back(), insert() и т.п. должны поддерживать размер () вектора во время работы, что является избыточными операциями при выполнении в узком цикле.

Поэтому наиболее эффективным способом является выделение памяти один раз, а затем копирование данных непосредственно в нее без сохранения каких-либо других переменных.

Это сделано так:

#include <iostream>
#include <array>
#include <vector>

using colour_fill = std::array<uint8_t, 4>;
using pixel_map = std::vector<uint8_t>;

pixel_map make_colour_texture(size_t width, size_t height, colour_fill colour)
{
    // allocate the buffer
    std::vector<uint8_t> pixels(width * height * sizeof(colour_fill));

    auto current = pixels.data();
    auto last = current + pixels.size();

    while (current != last) {
        current = std::copy(begin(colour), end(colour), current);
    }

    return pixels;
}

auto main() -> int
{
    colour_fill blue { 0, 0, 255, 255 };
    auto blue_bits = make_colour_texture(100, 100, blue);

    return 0;
}

Ты можешь использовать reserve предварительно выделить вектор; это позволит избежать перераспределения. Вы также можете определить небольшую последовательность (вероятно, вектор в стиле C:

char const init[] = { 0, 0, 255, 255 };

и цикл, вставляющий это в конец вектора:

for ( int i = 0; i < pixelCount; ++ i ) {
    v.insert( v.end(), std::begin( init ), std::end( init ) );
}

это лишь немного более эффективно, чем использование четырех push_back в цикле, но более лаконичен и, возможно, проясняет, что вы делаете, хотя и незначительно: большое преимущество может заключаться в возможности дать имя последовательности инициализации (например, что-то вроде defaultBackground).

Я сделал эту функцию шаблона, которая изменит свой входной контейнер, чтобы содержать count раз, что он уже содержит.

#include <iostream>
#include <vector>
#include <algorithm>

template<typename Container>
void repeat_pattern(Container& data, std::size_t count) {
  auto pattern_size = data.size();
  if(count == 0 or pattern_size == 0) {
    return;
  }
  data.resize(pattern_size * count);
  const auto pbeg = data.begin();
  const auto pend = std::next(pbeg, pattern_size);
  auto it = std::next(data.begin(), pattern_size);
  for(std::size_t k = 1; k < count; ++k) {
    std::copy(pbeg, pend, it);
    std::advance(it, pattern_size);
  }
}

template<typename Container>
void show(const Container& data) {
  for(const auto & item : data) {
    std::cout << item << " ";
  }
  std::cout << std::endl;
}

int main() {
  std::vector<int> v{1, 2, 3, 4};
  repeat_pattern(v, 3);
  // should show three repetitions of times 1, 2, 3, 4
  show(v);
}

Вывод (составлен как g++ example.cpp -std=c++14 -Wall -Wextra):

1 2 3 4 1 2 3 4 1 2 3 4

Я бы зарезервировал весь необходимый вам размер, а затем использовал бы функцию вставки, чтобы многократно добавлять шаблон в вектор.

std::array<unsigned char, 4> pattern{0, 0, 255, 255};
std::vector<unsigned char> blue_texture;
blue_texture.reserve(width * height * 4);

for (int i = 0; i < (width * height); ++i)
{
    blue_texture.insert(blue_texture.end(), pattern.begin(), pattern.end());
}
Другие вопросы по тегам