Skip to content
This repository was archived by the owner on Mar 15, 2025. It is now read-only.

Commit b29cdc9

Browse files
committed
Add mipmapping support to texture atlas.
1 parent 2130a2d commit b29cdc9

9 files changed

+283
-51
lines changed

dang-gl/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@ add_library(${PROJECT_NAME}
1010
src/Context/StateTypes.cpp
1111
src/General/GLConstants.cpp
1212
src/Image/BorderedImage.cpp
13+
src/Image/BorderedImageMipmapper.cpp
1314
src/Image/Image.cpp
1415
src/Image/ImageBorder.cpp
16+
src/Image/ImageMipmapper.cpp
17+
src/Image/MipmapLevels.cpp
1518
src/Image/Pixel.cpp
1619
src/Image/PixelFormat.cpp
1720
src/Image/PixelInternalFormat.cpp
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#pragma once
2+
3+
#include "dang-gl/Image/BorderedImage.h"
4+
#include "dang-gl/Image/ImageMipmapper.h"
5+
#include "dang-gl/Image/PixelFormat.h"
6+
#include "dang-gl/Image/PixelType.h"
7+
8+
#include "dang-math/vector.h"
9+
10+
namespace dang::gl {
11+
12+
/// @brief Mipmaps a bordered image into a new bordered image (with border set to "none").
13+
template <typename TCalcType = float>
14+
struct BorderedImageMipmapper {
15+
template <std::size_t v_dim, PixelFormat v_pixel_format, PixelType v_pixel_type, std::size_t v_row_alignment>
16+
auto operator()(const BorderedImage<v_dim, v_pixel_format, v_pixel_type, v_row_alignment>& bordered_image) const
17+
{
18+
// Mipmaps entire image including border, but sets border of result to "none".
19+
using BorderedImage = BorderedImage<v_dim, v_pixel_format, v_pixel_type, v_row_alignment>;
20+
return BorderedImage(image_mipmapper<TCalcType>(bordered_image.image()));
21+
}
22+
};
23+
24+
template <typename TCalcType = float>
25+
inline constexpr BorderedImageMipmapper<TCalcType> bordered_image_mipmapper;
26+
27+
} // namespace dang::gl
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
#pragma once
2+
3+
#include "dang-gl/Image/Image.h"
4+
#include "dang-gl/Image/PixelFormat.h"
5+
#include "dang-gl/Image/PixelType.h"
6+
7+
#include "dang-math/vector.h"
8+
9+
namespace dang::gl {
10+
11+
/// @brief Mipmaps a regular image using a 2x2 box filter.
12+
/// @remark Odd pixels are copied over, which might result in bad mipmaps, if the size is odd on a lot of levels.
13+
template <typename TCalcType = float>
14+
struct ImageMipmapper {
15+
template <std::size_t v_dim, PixelFormat v_pixel_format, PixelType v_pixel_type, std::size_t v_row_alignment>
16+
auto operator()(const Image<v_dim, v_pixel_format, v_pixel_type, v_row_alignment>& image) const
17+
{
18+
using Image = Image<v_dim, v_pixel_format, v_pixel_type, v_row_alignment>;
19+
using Pixel = typename Image::Pixel;
20+
using Size = typename Image::Size;
21+
using CalcPixel = dmath::Vector<TCalcType, Pixel::dim>;
22+
23+
// It is not possible to change box_size at will.
24+
// To get that working more changes would be necessary.
25+
constexpr std::size_t box_size = 2;
26+
constexpr auto box_pixels = box_size * box_size;
27+
28+
auto floor_size = image.size() / box_size;
29+
auto ceil_size = (image.size() - 1) / box_size + 1;
30+
Image result(ceil_size);
31+
for (const auto& pos : dmath::sbounds2(floor_size)) {
32+
CalcPixel color;
33+
for (const auto& offset : dmath::sbounds2(box_size))
34+
color += static_cast<CalcPixel>(image[pos * box_size + offset]);
35+
result[pos] = static_cast<Pixel>(color / box_pixels);
36+
}
37+
38+
auto odd_width = floor_size.x() != ceil_size.x();
39+
auto odd_height = floor_size.y() != ceil_size.y();
40+
41+
if (odd_width) {
42+
auto x = floor_size.x();
43+
for (std::size_t y = 0; y < floor_size.y(); y++) {
44+
CalcPixel color;
45+
for (std::size_t offset = 0; offset < box_size; offset++)
46+
color += static_cast<CalcPixel>(image[Size(x * box_size, y * box_size + offset)]);
47+
result[Size(x, y)] = static_cast<Pixel>(color / box_size);
48+
}
49+
}
50+
51+
if (odd_height) {
52+
auto y = floor_size.y();
53+
for (std::size_t x = 0; x < floor_size.x(); x++) {
54+
CalcPixel color;
55+
for (std::size_t offset = 0; offset < box_size; offset++)
56+
color += static_cast<CalcPixel>(image[Size(x * box_size + offset, y * box_size)]);
57+
result[Size(x, y)] = static_cast<Pixel>(color / box_size);
58+
}
59+
}
60+
61+
if (odd_width && odd_height)
62+
result[floor_size] = image[floor_size * box_size];
63+
64+
return result;
65+
}
66+
};
67+
68+
template <typename TCalcType = float>
69+
inline constexpr ImageMipmapper<TCalcType> image_mipmapper;
70+
71+
} // namespace dang::gl
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
#pragma once
2+
3+
#include "dang-gl/global.h"
4+
5+
#include "dang-math/vector.h"
6+
7+
#include "dang-utils/utils.h"
8+
9+
namespace dang::gl {
10+
11+
/// @brief Calculates the number of required mipmap levels for the given texture size.
12+
constexpr std::size_t maxMipmapLevels(std::size_t size) { return dutils::ilog2ceil(size) + 1; }
13+
14+
/// @brief Returns the next mipmap level size.
15+
/// @remarks Rounds up, which is unusual for mipmaps, but necessary when used in a texture atlas.
16+
constexpr dmath::svec2 nextMipmapSize(const dmath::svec2& size) { return (size.maxValue() - 1) / 2 + 1; }
17+
18+
/// @brief Combines several mipmap levels of the same image.
19+
template <typename TBorderedImageData>
20+
class MipmapLevels {
21+
public:
22+
using BorderedImageData = TBorderedImageData;
23+
using Image = typename BorderedImageData::Image;
24+
25+
/// @brief Only stores the given image without generating any additional mipmaps.
26+
MipmapLevels(Image full_image)
27+
: mipmap_levels_{std::move(full_image)}
28+
{}
29+
30+
/// @brief Only stores the given image without generating any additional mipmaps.
31+
MipmapLevels(BorderedImageData full_image)
32+
: mipmap_levels_{std::move(full_image)}
33+
{}
34+
35+
/// @brief Stores the given image and all mipmap levels using the provided mipmapper.
36+
template <typename TMipmapper>
37+
MipmapLevels(Image full_image, TMipmapper mipmapper)
38+
: mipmap_levels_(generateMipmapLevels(std::move(full_image), mipmapper))
39+
{}
40+
41+
/// @brief Stores the given bordered image and all mipmap levels using the provided mipmapper.
42+
template <typename TMipmapper>
43+
MipmapLevels(BorderedImageData full_image, TMipmapper mipmapper)
44+
: mipmap_levels_(generateMipmapLevels(std::move(full_image), mipmapper))
45+
{}
46+
47+
/// @brief The full image with the highest resolution.
48+
auto& fullImage() { return mipmap_levels_.front(); }
49+
/// @brief The full image with the highest resolution.
50+
const auto& fullImage() const { return mipmap_levels_.front(); }
51+
52+
/// @brief The total number of mipmap levels, including the original, full size image.
53+
auto count() const { return mipmap_levels_.size(); }
54+
55+
/// @brief A specific mipmap level with the given index, where 0 gives the original, full size image.
56+
auto& operator[](std::size_t index) { return mipmap_levels_[index]; }
57+
/// @brief A specific mipmap level with the given index, where 0 gives the original, full size image.
58+
const auto& operator[](std::size_t index) const { return mipmap_levels_[index]; }
59+
60+
auto begin() { return mipmap_levels_.begin(); }
61+
auto begin() const { return mipmap_levels_.begin(); }
62+
auto end() { return mipmap_levels_.end(); }
63+
auto end() const { return mipmap_levels_.end(); }
64+
65+
private:
66+
/// @brief Uses the given mipmapper to generate a vector of all mipmap levels for the given image.
67+
template <typename TMipmapper>
68+
static auto generateMipmapLevels(BorderedImageData&& full_image, TMipmapper mipmapper)
69+
{
70+
auto count = maxMipmapLevels(full_image.size().maxValue());
71+
std::vector<BorderedImageData> mipmap_levels{std::move(full_image)};
72+
for (std::size_t index = 1; index < count; index++) {
73+
const auto& prev = mipmap_levels.back();
74+
auto mipmapped = mipmapper(prev);
75+
ensureSizeHalved(prev.size(), mipmapped.size());
76+
mipmap_levels.push_back(std::move(mipmapped));
77+
}
78+
return mipmap_levels;
79+
}
80+
81+
/// @brief Throws an exception if "halved" isn't the correct next mipmap level size for "original".
82+
static void ensureSizeHalved(dmath::svec2 original, dmath::svec2 halved)
83+
{
84+
if (halved != nextMipmapSize(original))
85+
throw std::invalid_argument("mipmapper did not properly half the image size");
86+
}
87+
88+
std::vector<BorderedImageData> mipmap_levels_;
89+
};
90+
91+
} // namespace dang::gl

dang-gl/include/dang-gl/Texturing/TextureAtlasBase.h

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,28 +24,24 @@ class BasicFrozenTextureAtlas;
2424
template <typename TTextureBase>
2525
class TextureAtlasBase : public TTextureBase {
2626
public:
27-
using BorderedImageData = typename TTextureBase::BorderedImageData;
27+
using TextureBase = TTextureBase;
28+
using BorderedImageData = typename TextureBase::BorderedImageData;
2829
using Tiles = TextureAtlasTiles<BorderedImageData>;
2930
using TileHandle = typename Tiles::TileHandle;
30-
using Frozen = BasicFrozenTextureAtlas<TTextureBase>;
31+
using MipmapLevels = typename Tiles::MipmapLevels;
32+
using Frozen = BasicFrozenTextureAtlas<TextureBase>;
3133

3234
TextureAtlasBase(const TextureAtlasLimits& limits)
3335
: tiles_(limits)
3436
{}
3537

36-
[[nodiscard]] TileHandle add(BorderedImageData bordered_image_data)
37-
{
38-
return tiles_.add(std::move(bordered_image_data));
39-
}
38+
[[nodiscard]] TileHandle add(MipmapLevels mipmap_levels) { return tiles_.add(std::move(mipmap_levels)); }
4039

41-
void add(std::string name, BorderedImageData bordered_image_data)
42-
{
43-
tiles_.add(std::move(name), std::move(bordered_image_data));
44-
}
40+
void add(std::string name, MipmapLevels mipmap_levels) { tiles_.add(std::move(name), std::move(mipmap_levels)); }
4541

46-
[[nodiscard]] TileHandle addWithHandle(std::string name, BorderedImageData bordered_image_data)
42+
[[nodiscard]] TileHandle addWithHandle(std::string name, MipmapLevels mipmap_levels)
4743
{
48-
return tiles_.addWithHandle(std::move(name), std::move(bordered_image_data));
44+
return tiles_.addWithHandle(std::move(name), std::move(mipmap_levels));
4945
}
5046

5147
[[nodiscard]] bool exists(const TileHandle& tile_handle) const { return tiles_.exists(tile_handle); }
@@ -82,19 +78,20 @@ class TextureAtlasBase : public TTextureBase {
8278
template <typename TTextureBase>
8379
class BasicFrozenTextureAtlas : public TTextureBase {
8480
public:
85-
using BorderedImageData = typename TTextureBase::BorderedImageData;
81+
using TextureBase = TTextureBase;
82+
using BorderedImageData = typename TextureBase::BorderedImageData;
8683
using Tiles = FrozenTextureAtlasTiles<BorderedImageData>;
8784
using TileHandle = typename Tiles::TileHandle;
8885

89-
friend class TextureAtlasBase<TTextureBase>;
86+
friend class TextureAtlasBase<TextureBase>;
9087

9188
[[nodiscard]] bool exists(const TileHandle& tile_handle) const { return tiles_.exists(tile_handle); }
9289
[[nodiscard]] bool exists(const std::string& name) const { return tiles_.exists(name); }
9390
[[nodiscard]] TileHandle operator[](const std::string& name) const { return tiles_[name]; }
9491

9592
private:
96-
BasicFrozenTextureAtlas(Tiles&& tiles, TTextureBase&& texture)
97-
: TTextureBase(std::move(texture))
93+
BasicFrozenTextureAtlas(Tiles&& tiles, TextureBase&& texture)
94+
: TextureBase(std::move(texture))
9895
, tiles_(std::move(tiles))
9996
{}
10097

0 commit comments

Comments
 (0)