mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2026-01-23 06:44:38 +01:00
312 lines
9.5 KiB
C++
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 */
|