1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-01-23 06:44:38 +01:00
Files
OpenRCT2/src/drawing/engines/opengl/TextureCache.cpp
2016-07-27 16:31:25 +02:00

312 lines
9.5 KiB
C++

#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#ifndef DISABLE_OPENGL
#include <vector>
#include <stdexcept>
#include "../../../core/Memory.hpp"
#include "TextureCache.h"
extern "C"
{
#include "../../drawing.h"
}
TextureCache::TextureCache()
{
}
TextureCache::~TextureCache()
{
FreeTextures();
}
void TextureCache::SetPalette(const SDL_Color * palette)
{
Memory::CopyArray(_palette, palette, 256);
}
void TextureCache::InvalidateImage(uint32 image)
{
auto kvp = _imageTextureMap.find(image);
if (kvp != _imageTextureMap.end())
{
_atlases[kvp->second.index].Free(kvp->second);
_imageTextureMap.erase(kvp);
}
}
CachedTextureInfo TextureCache::GetOrLoadImageTexture(uint32 image)
{
auto kvp = _imageTextureMap.find(image & 0x7FFFF);
if (kvp != _imageTextureMap.end())
{
return kvp->second;
}
auto cacheInfo = LoadImageTexture(image);
_imageTextureMap[image & 0x7FFFF] = cacheInfo;
return cacheInfo;
}
CachedTextureInfo TextureCache::GetOrLoadGlyphTexture(uint32 image, uint8 * palette)
{
GlyphId glyphId;
glyphId.Image = image;
Memory::Copy<void>(&glyphId.Palette, palette, sizeof(glyphId.Palette));
auto kvp = _glyphTextureMap.find(glyphId);
if (kvp != _glyphTextureMap.end())
{
return kvp->second;
}
auto cacheInfo = LoadGlyphTexture(image, palette);
_glyphTextureMap[glyphId] = cacheInfo;
return cacheInfo;
}
void TextureCache::CreateAtlasesTexture()
{
if (!_atlasesTextureInitialised)
{
// Determine width and height to use for texture atlases
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &_atlasesTextureDimensions);
if (_atlasesTextureDimensions > TEXTURE_CACHE_MAX_ATLAS_SIZE) {
_atlasesTextureDimensions = TEXTURE_CACHE_MAX_ATLAS_SIZE;
}
// Determine maximum number of atlases (minimum of size and array limit)
glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &_atlasesTextureIndicesLimit);
if (_atlasesTextureDimensions < _atlasesTextureIndicesLimit) _atlasesTextureIndicesLimit = _atlasesTextureDimensions;
// Create an array texture to hold all of the atlases
glGenTextures(1, &_atlasesTexture);
glBindTexture(GL_TEXTURE_2D_ARRAY, _atlasesTexture);
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);
_atlasesTextureInitialised = true;
_atlasesTextureIndices = 0;
}
}
void TextureCache::EnlargeAtlasesTexture(GLuint newEntries)
{
CreateAtlasesTexture();
GLuint newIndices = _atlasesTextureIndices + newEntries;
// Retrieve current array data
auto oldPixels = std::vector<char>(_atlasesTextureDimensions * _atlasesTextureDimensions * _atlasesTextureIndices);
glGetTexImage(GL_TEXTURE_2D_ARRAY, 0, GL_RED_INTEGER, GL_UNSIGNED_BYTE, oldPixels.data());
// Reallocate array
glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_R8UI, _atlasesTextureDimensions, _atlasesTextureDimensions, newIndices, 0, GL_RED_INTEGER, GL_UNSIGNED_BYTE, nullptr);
// Restore old data
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, _atlasesTextureDimensions, _atlasesTextureDimensions, _atlasesTextureIndices, GL_RED_INTEGER, GL_UNSIGNED_BYTE, oldPixels.data());
_atlasesTextureIndices = newIndices;
}
CachedTextureInfo TextureCache::LoadImageTexture(uint32 image)
{
rct_drawpixelinfo * dpi = GetImageAsDPI(image, 0);
auto cacheInfo = AllocateImage(dpi->width, dpi->height);
glBindTexture(GL_TEXTURE_2D_ARRAY, _atlasesTexture);
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 cacheInfo;
}
CachedTextureInfo TextureCache::LoadGlyphTexture(uint32 image, uint8 * palette)
{
rct_drawpixelinfo * dpi = GetGlyphAsDPI(image, palette);
auto cacheInfo = AllocateImage(dpi->width, dpi->height);
glBindTexture(GL_TEXTURE_2D_ARRAY, _atlasesTexture);
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 cacheInfo;
}
void * TextureCache::GetImageAsARGB(uint32 image, uint32 tertiaryColour, uint32 * outWidth, uint32 * outHeight)
{
rct_g1_element * g1Element = gfx_get_g1_element(image & 0x7FFFF);
sint32 width = g1Element->width;
sint32 height = g1Element->height;
rct_drawpixelinfo * dpi = CreateDPI(width, height);
gfx_draw_sprite_software(dpi, image, -g1Element->x_offset, -g1Element->y_offset, tertiaryColour);
void * pixels32 = ConvertDPIto32bpp(dpi);
DeleteDPI(dpi);
*outWidth = width;
*outHeight = height;
return pixels32;
}
CachedTextureInfo TextureCache::AllocateImage(int imageWidth, int imageHeight)
{
CreateAtlasesTexture();
// Find an atlas that fits this image
for (Atlas& atlas : _atlases)
{
if (atlas.GetFreeSlots() > 0 && atlas.IsImageSuitable(imageWidth, imageHeight))
{
return atlas.Allocate(imageWidth, imageHeight);
}
}
// If there is no such atlas, then create a new one
if ((int) _atlases.size() >= _atlasesTextureIndicesLimit)
{
throw std::runtime_error("more texture atlases required, but device limit reached!");
}
int atlasIndex = (int) _atlases.size();
int atlasSize = (int) powf(2, (float) Atlas::CalculateImageSizeOrder(imageWidth, imageHeight));
#ifdef DEBUG
log_verbose("new texture atlas #%d (size %d) allocated\n", atlasIndex, atlasSize);
#endif
_atlases.push_back(std::move(Atlas(atlasIndex, atlasSize)));
_atlases.back().Initialise(_atlasesTextureDimensions, _atlasesTextureDimensions);
// Enlarge texture array to support new atlas
EnlargeAtlasesTexture(1);
// And allocate from the new atlas
return _atlases.back().Allocate(imageWidth, imageHeight);
}
rct_drawpixelinfo * TextureCache::GetImageAsDPI(uint32 image, uint32 tertiaryColour)
{
rct_g1_element * g1Element = gfx_get_g1_element(image & 0x7FFFF);
sint32 width = g1Element->width;
sint32 height = g1Element->height;
rct_drawpixelinfo * dpi = CreateDPI(width, height);
gfx_draw_sprite_software(dpi, image, -g1Element->x_offset, -g1Element->y_offset, tertiaryColour);
return dpi;
}
void * TextureCache::GetGlyphAsARGB(uint32 image, uint8 * palette, uint32 * outWidth, uint32 * outHeight)
{
rct_g1_element * g1Element = gfx_get_g1_element(image & 0x7FFFF);
sint32 width = g1Element->width;
sint32 height = g1Element->height;
rct_drawpixelinfo * dpi = CreateDPI(width, height);
gfx_draw_sprite_palette_set_software(dpi, image, -g1Element->x_offset, -g1Element->y_offset, palette, nullptr);
void * pixels32 = ConvertDPIto32bpp(dpi);
DeleteDPI(dpi);
*outWidth = width;
*outHeight = height;
return pixels32;
}
rct_drawpixelinfo * TextureCache::GetGlyphAsDPI(uint32 image, uint8 * palette)
{
rct_g1_element * g1Element = gfx_get_g1_element(image & 0x7FFFF);
sint32 width = g1Element->width;
sint32 height = g1Element->height;
rct_drawpixelinfo * dpi = CreateDPI(width, height);
gfx_draw_sprite_palette_set_software(dpi, image, -g1Element->x_offset, -g1Element->y_offset, palette, nullptr);
return dpi;
}
void * TextureCache::ConvertDPIto32bpp(const rct_drawpixelinfo * dpi)
{
size_t numPixels = dpi->width * dpi->height;
uint8 * pixels32 = Memory::Allocate<uint8>(numPixels * 4);
uint8 * src = dpi->bits;
uint8 * dst = pixels32;
for (size_t i = 0; i < numPixels; i++)
{
uint8 paletteIndex = *src++;
if (paletteIndex == 0)
{
// Transparent
*dst++ = 0;
*dst++ = 0;
*dst++ = 0;
*dst++ = 0;
}
else
{
SDL_Color colour = _palette[paletteIndex];
*dst++ = colour.r;
*dst++ = colour.g;
*dst++ = colour.b;
*dst++ = colour.a;
}
}
return pixels32;
}
void TextureCache::FreeTextures()
{
// Free array texture
glDeleteTextures(1, &_atlasesTexture);
}
rct_drawpixelinfo * TextureCache::CreateDPI(sint32 width, sint32 height)
{
size_t numPixels = width * height;
uint8 * pixels8 = Memory::Allocate<uint8>(numPixels);
Memory::Set(pixels8, 0, numPixels);
rct_drawpixelinfo * dpi = new rct_drawpixelinfo();
dpi->bits = pixels8;
dpi->pitch = 0;
dpi->x = 0;
dpi->y = 0;
dpi->width = width;
dpi->height = height;
dpi->zoom_level = 0;
return dpi;
}
void TextureCache::DeleteDPI(rct_drawpixelinfo* dpi)
{
Memory::Free(dpi->bits);
delete dpi;
}
GLuint TextureCache::GetAtlasesTexture()
{
return _atlasesTexture;
}
#endif /* DISABLE_OPENGL */