OpenRCT2/src/openrct2-ui/drawing/engines/opengl/TextureCache.h

249 lines
7.2 KiB
C++

/*****************************************************************************
* Copyright (c) 2014-2024 OpenRCT2 developers
*
* For a complete list of all authors, please refer to contributors.md
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is licensed under the GNU General Public License version 3.
*****************************************************************************/
#pragma once
#include "GLSLTypes.h"
#include "OpenGLAPI.h"
#include <SDL_pixels.h>
#include <algorithm>
#include <array>
#include <mutex>
#include <openrct2/common.h>
#include <openrct2/drawing/Drawing.h>
#include <openrct2/sprites.h>
#ifndef __MACOSX__
# include <shared_mutex>
#endif
#include <unordered_map>
#include <vector>
struct DrawPixelInfo;
struct PaletteMap;
enum class FilterPaletteID : int32_t;
struct GlyphId
{
ImageIndex Image;
uint64_t Palette;
struct Hash
{
size_t operator()(const GlyphId& k) const
{
size_t hash = k.Image * 7;
hash += (k.Palette & 0xFFFFFFFFUL) * 13;
hash += (k.Palette >> 32uL) * 23;
return hash;
}
};
struct Equal
{
bool operator()(const GlyphId& lhs, const GlyphId& rhs) const
{
return lhs.Image == rhs.Image && lhs.Palette == rhs.Palette;
}
};
};
// This is the maximum width and height of each atlas, basically the
// granularity at which new atlases are allocated (2048 -> 4 MB of VRAM)
constexpr int32_t TEXTURE_CACHE_MAX_ATLAS_SIZE = 2048;
// Pixel dimensions of smallest supported slots in texture atlases
// Must be a power of 2!
constexpr int32_t TEXTURE_CACHE_SMALLEST_SLOT = 32;
struct BasicTextureInfo
{
GLuint index;
vec4 normalizedBounds;
};
// Location of an image (texture atlas index, slot and normalized coordinates)
struct AtlasTextureInfo : public BasicTextureInfo
{
GLuint slot;
ivec4 bounds;
ImageIndex image;
};
// Represents a texture atlas that images of a given maximum size can be allocated from
// Atlases are all stored in the same 2D texture array, occupying the specified index
// Slots in atlases are always squares.
class Atlas final
{
private:
GLuint _index = 0;
int32_t _imageSize = 0;
int32_t _atlasWidth = 0;
int32_t _atlasHeight = 0;
std::vector<GLuint> _freeSlots;
int32_t _cols = 0;
int32_t _rows = 0;
public:
Atlas(GLuint index, int32_t imageSize)
: _index(index)
, _imageSize(imageSize)
{
}
void Initialise(int32_t atlasWidth, int32_t atlasHeight)
{
_atlasWidth = atlasWidth;
_atlasHeight = atlasHeight;
_cols = std::max(1, _atlasWidth / _imageSize);
_rows = std::max(1, _atlasHeight / _imageSize);
_freeSlots.resize(_cols * _rows);
for (size_t i = 0; i < _freeSlots.size(); i++)
{
_freeSlots[i] = static_cast<GLuint>(i);
}
}
AtlasTextureInfo Allocate(int32_t actualWidth, int32_t actualHeight)
{
assert(!_freeSlots.empty());
GLuint slot = _freeSlots.back();
_freeSlots.pop_back();
auto bounds = GetSlotCoordinates(slot, actualWidth, actualHeight);
AtlasTextureInfo info{};
info.index = _index;
info.slot = slot;
info.bounds = bounds;
info.normalizedBounds = NormalizeCoordinates(bounds);
return info;
}
void Free(const AtlasTextureInfo& info)
{
assert(_index == info.index);
_freeSlots.push_back(info.slot);
}
// Checks if specified image would be tightly packed in this atlas
// by checking if it is within the right power of 2 range
[[nodiscard]] bool IsImageSuitable(int32_t actualWidth, int32_t actualHeight) const
{
int32_t imageOrder = CalculateImageSizeOrder(actualWidth, actualHeight);
int32_t atlasOrder = log2(_imageSize);
return imageOrder == atlasOrder;
}
[[nodiscard]] int32_t GetFreeSlots() const
{
return static_cast<int32_t>(_freeSlots.size());
}
static int32_t CalculateImageSizeOrder(int32_t actualWidth, int32_t actualHeight)
{
int32_t actualSize = std::max(actualWidth, actualHeight);
if (actualSize < TEXTURE_CACHE_SMALLEST_SLOT)
{
actualSize = TEXTURE_CACHE_SMALLEST_SLOT;
}
return static_cast<int32_t>(ceil(log2f(static_cast<float>(actualSize))));
}
private:
[[nodiscard]] ivec4 GetSlotCoordinates(GLuint slot, int32_t actualWidth, int32_t actualHeight) const
{
int32_t row = slot / _cols;
int32_t col = slot % _cols;
return ivec4{
_imageSize * col,
_imageSize * row,
_imageSize * col + actualWidth,
_imageSize * row + actualHeight,
};
}
[[nodiscard]] vec4 NormalizeCoordinates(const ivec4& coords) const
{
return vec4{
coords.x / static_cast<float>(_atlasWidth),
coords.y / static_cast<float>(_atlasHeight),
coords.z / static_cast<float>(_atlasWidth),
coords.w / static_cast<float>(_atlasHeight),
};
}
};
class TextureCache final
{
private:
bool _initialized = false;
GLuint _atlasesTexture = 0;
GLint _atlasesTextureDimensions = 0;
GLuint _atlasesTextureCapacity = 0;
GLuint _atlasesTextureIndices = 0;
GLint _atlasesTextureIndicesLimit = 0;
std::vector<Atlas> _atlases;
std::unordered_map<GlyphId, AtlasTextureInfo, GlyphId::Hash, GlyphId::Equal> _glyphTextureMap;
std::vector<AtlasTextureInfo> _textureCache;
std::array<uint32_t, SPR_IMAGE_LIST_END> _indexMap;
GLuint _paletteTexture = 0;
GLuint _blendPaletteTexture = 0;
#ifndef __MACOSX__
std::shared_mutex _mutex;
using shared_lock = std::shared_lock<std::shared_mutex>;
using unique_lock = std::unique_lock<std::shared_mutex>;
#else
std::mutex _mutex;
using shared_lock = std::unique_lock<std::mutex>;
using unique_lock = std::unique_lock<std::mutex>;
#endif
public:
TextureCache();
~TextureCache();
void InvalidateImage(ImageIndex image);
BasicTextureInfo GetOrLoadImageTexture(const ImageId imageId);
BasicTextureInfo GetOrLoadGlyphTexture(const ImageId imageId, const PaletteMap& paletteMap);
BasicTextureInfo GetOrLoadBitmapTexture(ImageIndex image, const void* pixels, size_t width, size_t height);
GLuint GetAtlasesTexture();
GLuint GetPaletteTexture();
GLuint GetBlendPaletteTexture();
static GLint PaletteToY(FilterPaletteID palette);
private:
void CreateTextures();
void GeneratePaletteTexture();
void EnlargeAtlasesTexture(GLuint newEntries);
AtlasTextureInfo LoadImageTexture(const ImageId image);
AtlasTextureInfo LoadGlyphTexture(const ImageId image, const PaletteMap& paletteMap);
AtlasTextureInfo AllocateImage(int32_t imageWidth, int32_t imageHeight);
AtlasTextureInfo LoadBitmapTexture(ImageIndex image, const void* pixels, size_t width, size_t height);
static DrawPixelInfo GetImageAsDPI(const ImageId imageId);
static DrawPixelInfo GetGlyphAsDPI(const ImageId imageId, const PaletteMap& paletteMap);
void FreeTextures();
static DrawPixelInfo CreateDPI(int32_t width, int32_t height);
static void DeleteDPI(DrawPixelInfo dpi);
};