mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2026-01-15 11:03:00 +01:00
Implement multiple texture atlas system to handle small and large images
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
#version 150
|
||||
|
||||
uniform vec4 uPalette[256];
|
||||
uniform usampler2D uTexture;
|
||||
uniform usampler2DArray uTexture;
|
||||
|
||||
flat in ivec4 fClip;
|
||||
flat in int fFlags;
|
||||
in vec4 fColour;
|
||||
flat in int fTexAtlasIndex;
|
||||
in vec2 fTexColourCoords;
|
||||
in vec2 fTexMaskCoords;
|
||||
flat in int fMask;
|
||||
@@ -23,8 +24,8 @@ void main()
|
||||
discard;
|
||||
}
|
||||
|
||||
vec4 mask = uPalette[texture(uTexture, fTexMaskCoords).r];
|
||||
vec4 texel = uPalette[texture(uTexture, fTexColourCoords).r];
|
||||
vec4 mask = uPalette[texture(uTexture, vec3(fTexMaskCoords, float(fTexAtlasIndex))).r];
|
||||
vec4 texel = uPalette[texture(uTexture, vec3(fTexColourCoords, float(fTexAtlasIndex))).r];
|
||||
|
||||
if (fMask != 0)
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
uniform ivec2 uScreenSize;
|
||||
|
||||
in ivec4 ivClip;
|
||||
in int ivTexAtlasIndex;
|
||||
in vec4 ivTexColourBounds;
|
||||
in vec4 ivTexMaskBounds;
|
||||
in int ivFlags;
|
||||
@@ -17,6 +18,7 @@ out vec2 fPosition;
|
||||
flat out ivec4 fClip;
|
||||
flat out int fFlags;
|
||||
out vec4 fColour;
|
||||
flat out int fTexAtlasIndex;
|
||||
out vec2 fTexColourCoords;
|
||||
out vec2 fTexMaskCoords;
|
||||
flat out int fMask;
|
||||
@@ -58,6 +60,7 @@ void main()
|
||||
fFlags = ivFlags;
|
||||
fColour = ivColour;
|
||||
fMask = ivMask;
|
||||
fTexAtlasIndex = ivTexAtlasIndex;
|
||||
|
||||
gl_Position = vec4(pos, 0.0, 1.0);
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ DrawImageShader::DrawImageShader() : OpenGLShaderProgram("drawimage")
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, _vboInstances);
|
||||
glVertexAttribIPointer(vClip, 4, GL_INT, sizeof(DrawImageInstance), (void*) offsetof(DrawImageInstance, clip));
|
||||
glVertexAttribIPointer(vTexAtlasIndex, 1, GL_INT, sizeof(DrawImageInstance), (void*) offsetof(DrawImageInstance, texAtlasIndex));
|
||||
glVertexAttribPointer(vTexColourBounds, 4, GL_FLOAT, GL_FALSE, sizeof(DrawImageInstance), (void*) offsetof(DrawImageInstance, texColourBounds));
|
||||
glVertexAttribPointer(vTexMaskBounds, 4, GL_FLOAT, GL_FALSE, sizeof(DrawImageInstance), (void*) offsetof(DrawImageInstance, texMaskBounds));
|
||||
glVertexAttribIPointer(vFlags, 1, GL_INT, sizeof(DrawImageInstance), (void*) offsetof(DrawImageInstance, flags));
|
||||
@@ -45,6 +46,7 @@ DrawImageShader::DrawImageShader() : OpenGLShaderProgram("drawimage")
|
||||
|
||||
glEnableVertexAttribArray(vIndex);
|
||||
glEnableVertexAttribArray(vClip);
|
||||
glEnableVertexAttribArray(vTexAtlasIndex);
|
||||
glEnableVertexAttribArray(vTexColourBounds);
|
||||
glEnableVertexAttribArray(vTexMaskBounds);
|
||||
glEnableVertexAttribArray(vFlags);
|
||||
@@ -53,6 +55,7 @@ DrawImageShader::DrawImageShader() : OpenGLShaderProgram("drawimage")
|
||||
glEnableVertexAttribArray(vMask);
|
||||
|
||||
glVertexAttribDivisor(vClip, 1);
|
||||
glVertexAttribDivisor(vTexAtlasIndex, 1);
|
||||
glVertexAttribDivisor(vTexColourBounds, 1);
|
||||
glVertexAttribDivisor(vTexMaskBounds, 1);
|
||||
glVertexAttribDivisor(vFlags, 1);
|
||||
@@ -81,6 +84,7 @@ void DrawImageShader::GetLocations()
|
||||
|
||||
vIndex = GetAttributeLocation("vIndex");
|
||||
vClip = GetAttributeLocation("ivClip");
|
||||
vTexAtlasIndex = GetAttributeLocation("ivTexAtlasIndex");
|
||||
vTexColourBounds = GetAttributeLocation("ivTexColourBounds");
|
||||
vTexMaskBounds = GetAttributeLocation("ivTexMaskBounds");
|
||||
vFlags = GetAttributeLocation("ivFlags");
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
// Per-instance data for images
|
||||
struct DrawImageInstance {
|
||||
vec4i clip;
|
||||
int texAtlasIndex;
|
||||
vec4f texColourBounds;
|
||||
vec4f texMaskBounds;
|
||||
int flags;
|
||||
@@ -41,6 +42,7 @@ private:
|
||||
|
||||
GLuint vIndex;
|
||||
GLuint vClip;
|
||||
GLuint vTexAtlasIndex;
|
||||
GLuint vTexColourBounds;
|
||||
GLuint vTexMaskBounds;
|
||||
GLuint vFlags;
|
||||
|
||||
@@ -68,7 +68,8 @@ static const char * TryLoadAllProcAddresses()
|
||||
SetupOpenGLFunction(glTexImage2D);
|
||||
SetupOpenGLFunction(glTexParameteri);
|
||||
SetupOpenGLFunction(glViewport);
|
||||
SetupOpenGLFunction(glTexSubImage2D);
|
||||
SetupOpenGLFunction(glTexSubImage3D);
|
||||
SetupOpenGLFunction(glTexImage3D);
|
||||
|
||||
// 2.0+ functions
|
||||
SetupOpenGLFunction(glAttachShader);
|
||||
|
||||
@@ -40,7 +40,8 @@
|
||||
#define glTexImage2D __static__glTexImage2D
|
||||
#define glTexParameteri __static__glTexParameteri
|
||||
#define glViewport __static__glViewport
|
||||
#define glTexSubImage2D __static__glTexSubImage2D
|
||||
#define glTexSubImage3D __static__glTexSubImage3D
|
||||
#define glTexImage3D __static__glTexImage3D
|
||||
|
||||
#endif
|
||||
|
||||
@@ -68,7 +69,8 @@
|
||||
#undef glTexImage2D
|
||||
#undef glTexParameteri
|
||||
#undef glViewport
|
||||
#undef glTexSubImage2D
|
||||
#undef glTexSubImage3D
|
||||
#undef glTexImage3D
|
||||
|
||||
// 1.1 function signatures
|
||||
typedef void (APIENTRYP PFNGLBEGINPROC )(GLenum mode);
|
||||
@@ -89,7 +91,8 @@ typedef void (APIENTRYP PFNGLREADPIXELSPROC )(GLint x, GLint y, GLsizei wid
|
||||
typedef void (APIENTRYP PFNGLTEXIMAGE2DPROC )(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels);
|
||||
typedef void (APIENTRYP PFNGLTEXPARAMETERIPROC )(GLenum target, GLenum pname, GLint param);
|
||||
typedef void (APIENTRYP PFNGLVIEWPORTPROC )(GLint x, GLint y, GLsizei width, GLsizei height);
|
||||
typedef void (APIENTRYP PFNGLTEXSUBIMAGE2DPROC )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid* data);
|
||||
typedef void (APIENTRYP PFNGLTEXSUBIMAGE3DPROC )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid* data);
|
||||
typedef void (APIENTRYP PFNGLTEXIMAGE3DPROC )(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid * data);
|
||||
|
||||
#ifdef NO_EXTERN_GLAPI
|
||||
// Defines the function pointers
|
||||
@@ -121,7 +124,8 @@ GLAPI_DECL PFNGLREADPIXELSPROC glReadPixels GLAP
|
||||
GLAPI_DECL PFNGLTEXIMAGE2DPROC glTexImage2D GLAPI_SET;
|
||||
GLAPI_DECL PFNGLTEXPARAMETERIPROC glTexParameteri GLAPI_SET;
|
||||
GLAPI_DECL PFNGLVIEWPORTPROC glViewport GLAPI_SET;
|
||||
GLAPI_DECL PFNGLTEXSUBIMAGE2DPROC glTexSubImage2D GLAPI_SET;
|
||||
GLAPI_DECL PFNGLTEXSUBIMAGE3DPROC glTexSubImage3D GLAPI_SET;
|
||||
GLAPI_DECL PFNGLTEXIMAGE3DPROC glTexImage3D GLAPI_SET;
|
||||
|
||||
// 2.0+ function pointers
|
||||
GLAPI_DECL PFNGLATTACHSHADERPROC glAttachShader GLAPI_SET;
|
||||
|
||||
@@ -943,7 +943,7 @@ void OpenGLDrawingContext::FlushLines() {
|
||||
void OpenGLDrawingContext::FlushImages() {
|
||||
if (_commandBuffers.images.size() == 0) return;
|
||||
|
||||
OpenGLAPI::SetTexture(0, GL_TEXTURE_2D, _textureCache->GetAtlasTexture());
|
||||
OpenGLAPI::SetTexture(0, GL_TEXTURE_2D_ARRAY, _textureCache->GetAtlasTextureArray());
|
||||
|
||||
std::vector<DrawImageInstance> instances;
|
||||
instances.reserve(_commandBuffers.images.size());
|
||||
@@ -952,8 +952,9 @@ void OpenGLDrawingContext::FlushImages() {
|
||||
DrawImageInstance instance;
|
||||
|
||||
instance.clip = {command.clip[0], command.clip[1], command.clip[2], command.clip[3]};
|
||||
instance.texColourBounds = command.texColour.bounds;
|
||||
instance.texMaskBounds = command.texMask.bounds;
|
||||
instance.texAtlasIndex = command.texColour.index;
|
||||
instance.texColourBounds = command.texColour.normalizedBounds;
|
||||
instance.texMaskBounds = command.texMask.normalizedBounds;
|
||||
instance.flags = command.flags;
|
||||
instance.colour = command.colour;
|
||||
instance.bounds = {command.bounds[0], command.bounds[1], command.bounds[2], command.bounds[3]};
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#ifndef DISABLE_OPENGL
|
||||
|
||||
#include <vector>
|
||||
#include <stdexcept>
|
||||
#include "../../../core/Memory.hpp"
|
||||
#include "TextureCache.h"
|
||||
|
||||
@@ -41,19 +42,19 @@ void TextureCache::SetPalette(const SDL_Color * palette)
|
||||
|
||||
void TextureCache::InvalidateImage(uint32 image)
|
||||
{
|
||||
InitializeAtlasTexture();
|
||||
InitialiseAtlases();
|
||||
|
||||
auto kvp = _imageTextureMap.find(image);
|
||||
if (kvp != _imageTextureMap.end())
|
||||
{
|
||||
_atlases[kvp->second.index].Free(kvp->second);
|
||||
_imageTextureMap.erase(kvp);
|
||||
_freeSlots.push_back(kvp->second.slot);
|
||||
}
|
||||
}
|
||||
|
||||
CachedTextureInfo TextureCache::GetOrLoadImageTexture(uint32 image)
|
||||
{
|
||||
InitializeAtlasTexture();
|
||||
InitialiseAtlases();
|
||||
|
||||
auto kvp = _imageTextureMap.find(image & 0x7FFFF);
|
||||
if (kvp != _imageTextureMap.end())
|
||||
@@ -64,14 +65,12 @@ CachedTextureInfo TextureCache::GetOrLoadImageTexture(uint32 image)
|
||||
auto cacheInfo = LoadImageTexture(image);
|
||||
_imageTextureMap[image & 0x7FFFF] = cacheInfo;
|
||||
|
||||
//printf("%d slots left\n", (int) _freeSlots.size()); // TODO REMOVE
|
||||
|
||||
return cacheInfo;
|
||||
}
|
||||
|
||||
CachedTextureInfo TextureCache::GetOrLoadGlyphTexture(uint32 image, uint8 * palette)
|
||||
{
|
||||
InitializeAtlasTexture();
|
||||
InitialiseAtlases();
|
||||
|
||||
GlyphId glyphId;
|
||||
glyphId.Image = image;
|
||||
@@ -86,65 +85,49 @@ CachedTextureInfo TextureCache::GetOrLoadGlyphTexture(uint32 image, uint8 * pale
|
||||
auto cacheInfo = LoadGlyphTexture(image, palette);
|
||||
_glyphTextureMap[glyphId] = cacheInfo;
|
||||
|
||||
//printf("%d slots left\n", (int) _freeSlots.size()); // TODO REMOVE
|
||||
|
||||
return cacheInfo;
|
||||
}
|
||||
|
||||
void TextureCache::InitializeAtlasTexture() {
|
||||
if (!_atlasTextureInitialized) {
|
||||
glGenTextures(1, &_atlasTexture);
|
||||
glBindTexture(GL_TEXTURE_2D, _atlasTexture);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_R8UI, TEXTURE_CACHE_ATLAS_WIDTH, TEXTURE_CACHE_ATLAS_HEIGHT, 0, GL_RED_INTEGER, GL_UNSIGNED_BYTE, nullptr);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
void TextureCache::InitialiseAtlases() {
|
||||
if (!_atlasInitialised) {
|
||||
// Create an array texture to hold all of the atlases
|
||||
glGenTextures(1, &_atlasTextureArray);
|
||||
glBindTexture(GL_TEXTURE_2D_ARRAY, _atlasTextureArray);
|
||||
glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_R8UI, TEXTURE_CACHE_ATLAS_WIDTH, TEXTURE_CACHE_ATLAS_HEIGHT, _atlases.size(), 0, GL_RED_INTEGER, GL_UNSIGNED_BYTE, nullptr);
|
||||
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
|
||||
_freeSlots.resize(TEXTURE_CACHE_MAX_IMAGES);
|
||||
for (size_t i = 0; i < _freeSlots.size(); i++) _freeSlots[i] = i;
|
||||
|
||||
_atlasTextureInitialized = true;
|
||||
_atlasInitialised = true;
|
||||
}
|
||||
}
|
||||
|
||||
CachedTextureInfo TextureCache::LoadImageTexture(uint32 image)
|
||||
{
|
||||
rct_drawpixelinfo * dpi = GetImageAsDPI(image, 0);
|
||||
|
||||
GLuint slot = _freeSlots.back();
|
||||
_freeSlots.pop_back();
|
||||
|
||||
if (dpi->width > TEXTURE_CACHE_MAX_IMAGE_WIDTH) dpi->width = TEXTURE_CACHE_MAX_IMAGE_WIDTH;
|
||||
if (dpi->height > TEXTURE_CACHE_MAX_IMAGE_HEIGHT) dpi->height = TEXTURE_CACHE_MAX_IMAGE_HEIGHT;
|
||||
|
||||
vec4i coords = CalculateAtlasCoordinates(slot, dpi->width, dpi->height);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, _atlasTexture);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, coords.x, coords.y, dpi->width, dpi->height, GL_RED_INTEGER, GL_UNSIGNED_BYTE, dpi->bits);
|
||||
|
||||
auto cacheInfo = AllocateFromAppropriateAtlas(dpi->width, dpi->height);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D_ARRAY, _atlasTextureArray);
|
||||
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, cacheInfo.bounds.x, cacheInfo.bounds.y, cacheInfo.index, dpi->width, dpi->height, 1, GL_RED_INTEGER, GL_UNSIGNED_BYTE, dpi->bits);
|
||||
|
||||
DeleteDPI(dpi);
|
||||
|
||||
return {slot, ConvertToNormalizedCoordinates(coords)};
|
||||
return cacheInfo;
|
||||
}
|
||||
|
||||
CachedTextureInfo TextureCache::LoadGlyphTexture(uint32 image, uint8 * palette)
|
||||
{
|
||||
rct_drawpixelinfo * dpi = GetGlyphAsDPI(image, palette);
|
||||
|
||||
GLuint slot = _freeSlots.back();
|
||||
_freeSlots.pop_back();
|
||||
auto cacheInfo = AllocateFromAppropriateAtlas(dpi->width, dpi->height);
|
||||
|
||||
if (dpi->width > TEXTURE_CACHE_MAX_IMAGE_WIDTH) dpi->width = TEXTURE_CACHE_MAX_IMAGE_WIDTH;
|
||||
if (dpi->height > TEXTURE_CACHE_MAX_IMAGE_HEIGHT) dpi->height = TEXTURE_CACHE_MAX_IMAGE_HEIGHT;
|
||||
|
||||
vec4i coords = CalculateAtlasCoordinates(slot, dpi->width, dpi->height);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, _atlasTexture);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, coords.x, coords.y, dpi->width, dpi->height, GL_RED_INTEGER, GL_UNSIGNED_BYTE, dpi->bits);
|
||||
glBindTexture(GL_TEXTURE_2D_ARRAY, _atlasTextureArray);
|
||||
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, cacheInfo.bounds.x, cacheInfo.bounds.y, cacheInfo.index, dpi->width, dpi->height, 1, GL_RED_INTEGER, GL_UNSIGNED_BYTE, dpi->bits);
|
||||
|
||||
DeleteDPI(dpi);
|
||||
|
||||
return{slot, ConvertToNormalizedCoordinates(coords)};
|
||||
return cacheInfo;
|
||||
}
|
||||
|
||||
void * TextureCache::GetImageAsARGB(uint32 image, uint32 tertiaryColour, uint32 * outWidth, uint32 * outHeight)
|
||||
@@ -163,6 +146,16 @@ void * TextureCache::GetImageAsARGB(uint32 image, uint32 tertiaryColour, uint32
|
||||
return pixels32;
|
||||
}
|
||||
|
||||
CachedTextureInfo TextureCache::AllocateFromAppropriateAtlas(int imageWidth, int imageHeight) {
|
||||
for (Atlas& atlas : _atlases) {
|
||||
if (atlas.GetFreeSlots() > 0 && atlas.SupportsImage(imageWidth, imageHeight)) {
|
||||
return atlas.Allocate(imageWidth, imageHeight);
|
||||
}
|
||||
}
|
||||
|
||||
throw std::runtime_error("no atlas with free slots left that supports image!");
|
||||
}
|
||||
|
||||
rct_drawpixelinfo * TextureCache::GetImageAsDPI(uint32 image, uint32 tertiaryColour)
|
||||
{
|
||||
rct_g1_element * g1Element = gfx_get_g1_element(image & 0x7FFFF);
|
||||
@@ -233,28 +226,7 @@ void * TextureCache::ConvertDPIto32bpp(const rct_drawpixelinfo * dpi)
|
||||
void TextureCache::FreeTextures()
|
||||
{
|
||||
// Free array texture
|
||||
glDeleteTextures(1, &_atlasTexture);
|
||||
}
|
||||
|
||||
vec4i TextureCache::CalculateAtlasCoordinates(GLuint slot, int width, int height) {
|
||||
int row = slot / TEXTURE_CACHE_IMAGES_U;
|
||||
int col = slot % TEXTURE_CACHE_IMAGES_U;
|
||||
|
||||
return vec4i{
|
||||
TEXTURE_CACHE_MAX_IMAGE_WIDTH * col,
|
||||
TEXTURE_CACHE_MAX_IMAGE_HEIGHT * row,
|
||||
TEXTURE_CACHE_MAX_IMAGE_WIDTH * col + width,
|
||||
TEXTURE_CACHE_MAX_IMAGE_HEIGHT * row + height,
|
||||
};
|
||||
}
|
||||
|
||||
vec4f TextureCache::ConvertToNormalizedCoordinates(vec4i coordinates) {
|
||||
return vec4f{
|
||||
coordinates.x / (float) TEXTURE_CACHE_ATLAS_WIDTH,
|
||||
coordinates.y / (float) TEXTURE_CACHE_ATLAS_HEIGHT,
|
||||
coordinates.z / (float) TEXTURE_CACHE_ATLAS_WIDTH,
|
||||
coordinates.w / (float) TEXTURE_CACHE_ATLAS_HEIGHT
|
||||
};
|
||||
glDeleteTextures(1, &_atlasTextureArray);
|
||||
}
|
||||
|
||||
rct_drawpixelinfo * TextureCache::CreateDPI(sint32 width, sint32 height)
|
||||
@@ -280,8 +252,8 @@ void TextureCache::DeleteDPI(rct_drawpixelinfo* dpi)
|
||||
delete dpi;
|
||||
}
|
||||
|
||||
GLuint TextureCache::GetAtlasTexture() {
|
||||
return _atlasTexture;
|
||||
GLuint TextureCache::GetAtlasTextureArray() {
|
||||
return _atlasTextureArray;
|
||||
}
|
||||
|
||||
#endif /* DISABLE_OPENGL */
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <unordered_map>
|
||||
#include <array>
|
||||
#include <SDL_pixels.h>
|
||||
#include "../../../common.h"
|
||||
#include "OpenGLAPI.h"
|
||||
@@ -52,27 +53,108 @@ struct GlyphId
|
||||
};
|
||||
|
||||
// TODO: Derive from hardware limits instead
|
||||
// TODO: Support > 64x64 images
|
||||
// TODO: Handle no more slots remaining (allocate more atlases?)
|
||||
// TODO: Handle images larger than 256x256
|
||||
constexpr int TEXTURE_CACHE_ATLAS_WIDTH = 8192;
|
||||
constexpr int TEXTURE_CACHE_ATLAS_HEIGHT = 8192;
|
||||
constexpr int TEXTURE_CACHE_MAX_IMAGE_WIDTH = 64;
|
||||
constexpr int TEXTURE_CACHE_MAX_IMAGE_HEIGHT = 64;
|
||||
constexpr int TEXTURE_CACHE_IMAGES_U = TEXTURE_CACHE_ATLAS_WIDTH / TEXTURE_CACHE_MAX_IMAGE_WIDTH;
|
||||
constexpr int TEXTURE_CACHE_IMAGES_V = TEXTURE_CACHE_ATLAS_HEIGHT / TEXTURE_CACHE_MAX_IMAGE_HEIGHT;
|
||||
constexpr int TEXTURE_CACHE_MAX_IMAGES = TEXTURE_CACHE_IMAGES_U * TEXTURE_CACHE_IMAGES_V;
|
||||
|
||||
// Location of an image (texture atlas index, slot and normalized coordinates)
|
||||
struct CachedTextureInfo {
|
||||
GLuint index;
|
||||
GLuint slot;
|
||||
vec4f bounds;
|
||||
vec4i bounds;
|
||||
vec4f normalizedBounds;
|
||||
};
|
||||
|
||||
// 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
|
||||
class Atlas {
|
||||
private:
|
||||
GLuint _index;
|
||||
int _imageWidth, _imageHeight;
|
||||
std::vector<GLuint> _freeSlots;
|
||||
|
||||
int _cols, _rows;
|
||||
|
||||
public:
|
||||
Atlas(GLuint index, int imageWidth, int imageHeight) {
|
||||
_index = index;
|
||||
_imageWidth = imageWidth;
|
||||
_imageHeight = imageHeight;
|
||||
|
||||
_cols = TEXTURE_CACHE_ATLAS_WIDTH / imageWidth;
|
||||
_rows = TEXTURE_CACHE_ATLAS_HEIGHT / imageHeight;
|
||||
|
||||
_freeSlots.resize(_cols * _rows);
|
||||
for (size_t i = 0; i < _freeSlots.size(); i++) {
|
||||
_freeSlots[i] = i;
|
||||
}
|
||||
}
|
||||
|
||||
CachedTextureInfo Allocate(int actualWidth, int actualHeight) {
|
||||
assert(_freeSlots.size() > 0);
|
||||
|
||||
GLuint slot = _freeSlots.back();
|
||||
_freeSlots.pop_back();
|
||||
|
||||
auto bounds = GetSlotCoordinates(slot, actualWidth, actualHeight);
|
||||
|
||||
#ifdef DEBUG
|
||||
printf("texture atlas (%d, %d) has %d slots left\n", _imageWidth, _imageHeight, GetFreeSlots());
|
||||
#endif
|
||||
|
||||
return {_index, slot, bounds, NormalizeCoordinates(bounds)};
|
||||
}
|
||||
|
||||
void Free(const CachedTextureInfo& info) {
|
||||
assert(_index == info.index);
|
||||
|
||||
_freeSlots.push_back(info.slot);
|
||||
}
|
||||
|
||||
bool SupportsImage(int actualWidth, int actualHeight) const {
|
||||
return actualWidth <= _imageWidth && actualHeight <= _imageHeight;
|
||||
}
|
||||
|
||||
int GetFreeSlots() const {
|
||||
return (int) _freeSlots.size();
|
||||
}
|
||||
|
||||
private:
|
||||
vec4i GetSlotCoordinates(GLuint slot, int actualWidth, int actualHeight) {
|
||||
int row = slot / _cols;
|
||||
int col = slot % _cols;
|
||||
|
||||
return vec4i{
|
||||
_imageWidth * col,
|
||||
_imageHeight * row,
|
||||
_imageWidth * col + actualWidth,
|
||||
_imageHeight * row + actualHeight,
|
||||
};
|
||||
}
|
||||
|
||||
static vec4f NormalizeCoordinates(const vec4i& coords) {
|
||||
return vec4f{
|
||||
coords.x / (float) TEXTURE_CACHE_ATLAS_WIDTH,
|
||||
coords.y / (float) TEXTURE_CACHE_ATLAS_HEIGHT,
|
||||
coords.z / (float) TEXTURE_CACHE_ATLAS_WIDTH,
|
||||
coords.w / (float) TEXTURE_CACHE_ATLAS_HEIGHT
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
class TextureCache
|
||||
{
|
||||
private:
|
||||
bool _atlasTextureInitialized = false;
|
||||
GLuint _atlasTexture;
|
||||
bool _atlasInitialised = false;
|
||||
|
||||
std::vector<GLuint> _freeSlots;
|
||||
GLuint _atlasTextureArray;
|
||||
|
||||
// Atlases should be ordered from small to large image support
|
||||
std::array<Atlas, 2> _atlases = {
|
||||
Atlas{0, 64, 64},
|
||||
Atlas{1, 256, 256}
|
||||
};
|
||||
|
||||
std::unordered_map<uint32, CachedTextureInfo> _imageTextureMap;
|
||||
std::unordered_map<GlyphId, CachedTextureInfo, GlyphId::Hash, GlyphId::Equal> _glyphTextureMap;
|
||||
@@ -87,20 +169,19 @@ public:
|
||||
CachedTextureInfo GetOrLoadImageTexture(uint32 image);
|
||||
CachedTextureInfo GetOrLoadGlyphTexture(uint32 image, uint8 * palette);
|
||||
|
||||
GLuint GetAtlasTexture();
|
||||
GLuint GetAtlasTextureArray();
|
||||
|
||||
private:
|
||||
void InitializeAtlasTexture();
|
||||
void InitialiseAtlases();
|
||||
CachedTextureInfo LoadImageTexture(uint32 image);
|
||||
CachedTextureInfo LoadGlyphTexture(uint32 image, uint8 * palette);
|
||||
CachedTextureInfo AllocateFromAppropriateAtlas(int imageWidth, int imageHeight);
|
||||
void * GetImageAsARGB(uint32 image, uint32 tertiaryColour, uint32 * outWidth, uint32 * outHeight);
|
||||
rct_drawpixelinfo * GetImageAsDPI(uint32 image, uint32 tertiaryColour);
|
||||
void * GetGlyphAsARGB(uint32 image, uint8 * palette, uint32 * outWidth, uint32 * outHeight);
|
||||
rct_drawpixelinfo * GetGlyphAsDPI(uint32 image, uint8 * palette);
|
||||
void * ConvertDPIto32bpp(const rct_drawpixelinfo * dpi);
|
||||
void FreeTextures();
|
||||
vec4i CalculateAtlasCoordinates(GLuint slot, int width, int height);
|
||||
vec4f ConvertToNormalizedCoordinates(vec4i coordinates);
|
||||
|
||||
static rct_drawpixelinfo * CreateDPI(sint32 width, sint32 height);
|
||||
static void DeleteDPI(rct_drawpixelinfo * dpi);
|
||||
|
||||
Reference in New Issue
Block a user