470 lines
13 KiB
C++
470 lines
13 KiB
C++
#include "render/TextureManager.h"
|
||
#include "render/OpenGlExtensions.h"
|
||
#ifdef PNG_ENABLED
|
||
#include "png.h"
|
||
#endif
|
||
#include <iostream>
|
||
|
||
namespace ZL
|
||
{
|
||
|
||
Texture::Texture(const TextureDataStruct& texData) {
|
||
width = texData.width;
|
||
height = texData.height;
|
||
glGenTextures(1, &texID);
|
||
glBindTexture(GL_TEXTURE_2D, texID);
|
||
|
||
GLint internalFormat;
|
||
GLenum externalFormat;
|
||
|
||
switch (texData.format) {
|
||
case TextureDataStruct::R8:
|
||
// Для WebGL 1.0 лучше использовать GL_LUMINANCE вместо GL_RED
|
||
// Если используешь WebGL 2.0, можно оставить GL_RED
|
||
#if defined(EMSCRIPTEN) || defined(__ANDROID__)
|
||
internalFormat = GL_LUMINANCE;
|
||
externalFormat = GL_LUMINANCE;
|
||
#else
|
||
internalFormat = GL_RED;
|
||
externalFormat = GL_RED;
|
||
#endif
|
||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||
// Исправлено: используем GL_TEXTURE_2D
|
||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||
break;
|
||
|
||
case TextureDataStruct::RGB:
|
||
internalFormat = GL_RGB;
|
||
externalFormat = GL_RGB;
|
||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // RGB может быть не выровнен по 4 байта
|
||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
||
break;
|
||
|
||
default: // RGBA
|
||
internalFormat = GL_RGBA;
|
||
externalFormat = GL_RGBA;
|
||
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
||
break;
|
||
}
|
||
|
||
glTexImage2D(GL_TEXTURE_2D, 0, internalFormat,
|
||
static_cast<GLsizei>(width), static_cast<GLsizei>(height),
|
||
0, externalFormat, GL_UNSIGNED_BYTE, texData.data.data());
|
||
CheckGlError(__FILE__, __LINE__);
|
||
|
||
// 3. Фильтрация и Мип-мапы
|
||
// ВНИМАНИЕ: Для шрифтов (NPOT) в WebGL glGenerateMipmap работать НЕ будет!
|
||
if (texData.mipmap == TextureDataStruct::GENERATE && texData.format != TextureDataStruct::R8) {
|
||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
||
glGenerateMipmap(GL_TEXTURE_2D);
|
||
}
|
||
else {
|
||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||
}
|
||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||
|
||
CheckGlError(__FILE__, __LINE__);
|
||
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||
}
|
||
|
||
Texture::Texture(const std::array<TextureDataStruct, 6>& texDataArray)
|
||
{
|
||
width = texDataArray[0].width;
|
||
height = texDataArray[0].height;
|
||
|
||
for (size_t i = 1; i < 6; ++i) {
|
||
if (texDataArray[i].width != width || texDataArray[i].height != height) {
|
||
throw std::runtime_error("Cubemap faces must have the same dimensions");
|
||
}
|
||
}
|
||
|
||
glGenTextures(1, &texID);
|
||
|
||
if (texID == 0)
|
||
{
|
||
throw std::runtime_error("glGenTextures did not work for cubemap");
|
||
}
|
||
|
||
glBindTexture(GL_TEXTURE_CUBE_MAP, texID);
|
||
|
||
CheckGlError(__FILE__, __LINE__);
|
||
|
||
|
||
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||
|
||
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||
|
||
#ifndef EMSCRIPTEN
|
||
#ifndef __ANDROID__
|
||
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
|
||
#endif
|
||
#endif
|
||
|
||
CheckGlError(__FILE__, __LINE__);
|
||
|
||
for (int i = 0; i < 6; ++i)
|
||
{
|
||
GLint internalFormat;
|
||
GLenum format;
|
||
|
||
|
||
if (texDataArray[i].format == TextureDataStruct::RGB)
|
||
{
|
||
internalFormat = GL_RGB; // internalFormat
|
||
format = GL_RGB; // format
|
||
}
|
||
else // BS_32BIT
|
||
{
|
||
internalFormat = GL_RGBA; // internalFormat
|
||
format = GL_RGBA; // format
|
||
}
|
||
|
||
glTexImage2D(
|
||
GL_TEXTURE_CUBE_MAP_POSITIVE_X + i,
|
||
0,
|
||
internalFormat,
|
||
static_cast<GLsizei>(width),
|
||
static_cast<GLsizei>(height),
|
||
0,
|
||
format,
|
||
GL_UNSIGNED_BYTE,
|
||
texDataArray[i].data.data()
|
||
);
|
||
CheckGlError(__FILE__, __LINE__);
|
||
}
|
||
|
||
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
|
||
}
|
||
|
||
Texture::~Texture()
|
||
{
|
||
glDeleteTextures(1, &texID);
|
||
texID = 0;
|
||
}
|
||
|
||
GLuint Texture::getTexID()
|
||
{
|
||
return texID;
|
||
}
|
||
|
||
size_t Texture::getWidth()
|
||
{
|
||
return width;
|
||
}
|
||
|
||
size_t Texture::getHeight()
|
||
{
|
||
return height;
|
||
}
|
||
|
||
|
||
|
||
TextureDataStruct CreateTextureDataFromBmp24(const std::string& fullFileName, const std::string& ZIPFileName)
|
||
{
|
||
|
||
TextureDataStruct texData;
|
||
std::vector<char> fileArr;
|
||
|
||
fileArr = !ZIPFileName.empty() ? readFileFromZIP(fullFileName, ZIPFileName) : readFile(fullFileName);
|
||
|
||
size_t fileSize = fileArr.size();
|
||
|
||
if (fileSize < 22)
|
||
{
|
||
throw std::runtime_error("File is too short or not correct!");
|
||
}
|
||
|
||
//This refers to BITMAPV5HEADER
|
||
texData.width = *reinterpret_cast<uint32_t*>(&fileArr[18]);
|
||
texData.height = *reinterpret_cast<uint32_t*>(&fileArr[22]);
|
||
|
||
texData.format = TextureDataStruct::RGB;
|
||
|
||
size_t dataSize = texData.width * texData.height * 3;
|
||
|
||
texData.data.resize(dataSize);
|
||
|
||
size_t pos = *reinterpret_cast<uint32_t*>(&fileArr[10]);
|
||
size_t x = 0;
|
||
|
||
for (size_t i = 0; i < texData.width; i++)
|
||
for (size_t j = 0; j < texData.height; j++)
|
||
{
|
||
|
||
if (pos + 3 > fileSize)
|
||
{
|
||
throw std::runtime_error("File is too short!");
|
||
}
|
||
|
||
|
||
x = (i * texData.height + j) + (i * texData.height + j) + (i * texData.height + j);
|
||
|
||
texData.data[x + 2] = fileArr[pos++];
|
||
texData.data[x + 1] = fileArr[pos++];
|
||
texData.data[x + 0] = fileArr[pos++];
|
||
|
||
}
|
||
|
||
return texData;
|
||
}
|
||
|
||
TextureDataStruct CreateTextureDataFromBmp32(const std::string& fullFileName, const std::string& ZIPFileName)
|
||
{
|
||
|
||
TextureDataStruct texData;
|
||
std::vector<char> fileArr;
|
||
|
||
fileArr = !ZIPFileName.empty() ? readFileFromZIP(fullFileName, ZIPFileName) : readFile(fullFileName);
|
||
|
||
size_t fileSize = fileArr.size();
|
||
|
||
if (fileSize < 22)
|
||
{
|
||
throw std::runtime_error("File is too short or not correct!");
|
||
}
|
||
|
||
//This refers to BITMAPV5HEADER
|
||
texData.width = *reinterpret_cast<uint32_t*>(&fileArr[18]);
|
||
texData.height = *reinterpret_cast<uint32_t*>(&fileArr[22]);
|
||
|
||
texData.format = TextureDataStruct::RGBA;
|
||
|
||
size_t dataSize = texData.width * texData.height * 4;
|
||
|
||
texData.data.resize(dataSize);
|
||
|
||
size_t pos = *reinterpret_cast<uint32_t*>(&fileArr[10]);
|
||
size_t x = 0;
|
||
|
||
for (size_t i = 0; i < texData.width; i++)
|
||
for (size_t j = 0; j < texData.height; j++)
|
||
{
|
||
|
||
if (pos + 4 > fileSize)
|
||
{
|
||
throw std::runtime_error("File is too short!");
|
||
}
|
||
|
||
|
||
x = (i * texData.height + j) + (i * texData.height + j) + (i * texData.height + j) + (i * texData.height + j);
|
||
|
||
texData.data[x + 2] = fileArr[pos++];
|
||
texData.data[x + 1] = fileArr[pos++];
|
||
texData.data[x + 0] = fileArr[pos++];
|
||
texData.data[x + 3] = fileArr[pos++];
|
||
|
||
}
|
||
|
||
return texData;
|
||
}
|
||
|
||
#ifdef PNG_ENABLED
|
||
|
||
struct png_data_t {
|
||
const char* data;
|
||
size_t size;
|
||
size_t offset;
|
||
};
|
||
|
||
void user_read_data(png_structp png_ptr, png_bytep out_ptr, png_size_t bytes_to_read) {
|
||
png_data_t* data = (png_data_t*)png_get_io_ptr(png_ptr);
|
||
|
||
if (data->offset + bytes_to_read > data->size) {
|
||
png_error(png_ptr, "PNG Read Error: Attempted to read past end of data buffer.");
|
||
bytes_to_read = data->size - data->offset;
|
||
}
|
||
|
||
std::memcpy(out_ptr, data->data + data->offset, bytes_to_read);
|
||
|
||
data->offset += bytes_to_read;
|
||
}
|
||
|
||
void user_warning_fn(png_structp png_ptr, png_const_charp warning_msg) {
|
||
std::cout << "PNG Warning: " << warning_msg << std::endl;
|
||
}
|
||
|
||
|
||
void user_error_fn(png_structp png_ptr, png_const_charp error_msg) {
|
||
|
||
std::cout << "PNG Error: " << error_msg << std::endl;
|
||
longjmp(png_jmpbuf(png_ptr), 1);
|
||
}
|
||
|
||
TextureDataStruct CreateTextureDataFromPng(const std::vector<char>& fileArr)
|
||
{
|
||
TextureDataStruct texData;
|
||
|
||
png_data_t png_data = { fileArr.data(), fileArr.size(), 0 };
|
||
|
||
png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
||
if (!png) {
|
||
throw std::runtime_error("Could not create PNG read structure");
|
||
}
|
||
|
||
png_infop info = png_create_info_struct(png);
|
||
if (!info) {
|
||
png_destroy_read_struct(&png, nullptr, nullptr);
|
||
throw std::runtime_error("Could not create PNG info structure");
|
||
}
|
||
|
||
if (setjmp(png_jmpbuf(png))) {
|
||
png_destroy_read_struct(&png, &info, nullptr);
|
||
throw std::runtime_error("Error during PNG read (longjmp was executed)");
|
||
}
|
||
|
||
png_set_error_fn(png, nullptr, user_error_fn, user_warning_fn);
|
||
|
||
png_set_read_fn(png, &png_data, user_read_data);
|
||
|
||
png_read_info(png, info);
|
||
|
||
texData.width = png_get_image_width(png, info);
|
||
texData.height = png_get_image_height(png, info);
|
||
png_byte color_type = png_get_color_type(png, info);
|
||
png_byte bit_depth = png_get_bit_depth(png, info);
|
||
|
||
if (bit_depth == 16)
|
||
png_set_strip_16(png);
|
||
|
||
if (color_type == PNG_COLOR_TYPE_PALETTE)
|
||
png_set_palette_to_rgb(png);
|
||
|
||
if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
|
||
png_set_expand_gray_1_2_4_to_8(png);
|
||
|
||
if (png_get_valid(png, info, PNG_INFO_tRNS))
|
||
png_set_tRNS_to_alpha(png);
|
||
|
||
if (color_type == PNG_COLOR_TYPE_RGB ||
|
||
color_type == PNG_COLOR_TYPE_GRAY ||
|
||
color_type == PNG_COLOR_TYPE_PALETTE)
|
||
png_set_filler(png, 0xFF, PNG_FILLER_AFTER);
|
||
|
||
if (color_type == PNG_COLOR_TYPE_GRAY ||
|
||
color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
|
||
png_set_gray_to_rgb(png);
|
||
|
||
png_read_update_info(png, info);
|
||
|
||
png_bytep* row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * texData.height);
|
||
for (int y = 0; y < texData.height; y++) {
|
||
row_pointers[y] = (png_byte*)malloc(png_get_rowbytes(png, info));
|
||
}
|
||
|
||
png_read_image(png, row_pointers);
|
||
|
||
bool has_alpha = (color_type & PNG_COLOR_MASK_ALPHA) || (png_get_valid(png, info, PNG_INFO_tRNS));
|
||
|
||
size_t dataSize;
|
||
|
||
if (has_alpha)
|
||
{
|
||
texData.format = TextureDataStruct::RGBA;
|
||
}
|
||
else
|
||
{
|
||
texData.format = TextureDataStruct::RGB;
|
||
}
|
||
|
||
int channels = has_alpha ? 4 : 3;
|
||
|
||
dataSize = texData.width * texData.height * channels;
|
||
texData.data.resize(dataSize);
|
||
|
||
|
||
for (int y = texData.height - 1; y >= 0; y--) {
|
||
png_bytep row = row_pointers[texData.height - 1 - y];
|
||
for (int x = 0; x < texData.width; x++) {
|
||
png_bytep px = &(row[x * 4]);
|
||
texData.data[(y * texData.width + x) * channels + 0] = px[0]; // R
|
||
texData.data[(y * texData.width + x) * channels + 1] = px[1]; // G
|
||
texData.data[(y * texData.width + x) * channels + 2] = px[2]; // B
|
||
if (has_alpha) {
|
||
texData.data[(y * texData.width + x) * channels + 3] = px[3]; // A
|
||
}
|
||
}
|
||
free(row_pointers[texData.height - 1 - y]);
|
||
}
|
||
free(row_pointers);
|
||
// ==================================================
|
||
|
||
png_destroy_read_struct(&png, &info, nullptr);
|
||
|
||
return texData;
|
||
}
|
||
|
||
|
||
TextureDataStruct CreateTextureDataFromPng(const std::string& fullFileName, const std::string& ZIPFileName)
|
||
{
|
||
std::vector<char> fileArr;
|
||
|
||
fileArr = !ZIPFileName.empty() ? readFileFromZIP(fullFileName, ZIPFileName) : readFile(fullFileName);
|
||
|
||
if (fileArr.empty()) {
|
||
std::cout << "Could not read file data into memory: " << fullFileName << " zip file name: " << ZIPFileName << std::endl;
|
||
throw std::runtime_error("Could not read file data into memory");
|
||
}
|
||
|
||
return CreateTextureDataFromPng(fileArr);
|
||
}
|
||
|
||
|
||
#endif
|
||
|
||
std::string TextureManager::MakeKey(const std::string& fileName, const std::string& zipFile)
|
||
{
|
||
return zipFile.empty() ? fileName : fileName + "|" + zipFile;
|
||
}
|
||
|
||
std::shared_ptr<Texture> TextureManager::LoadFromBmp24(const std::string& fileName, const std::string& zipFile)
|
||
{
|
||
std::string key = MakeKey(fileName, zipFile);
|
||
auto it = textureMap.find(key);
|
||
if (it != textureMap.end())
|
||
return it->second;
|
||
auto tex = std::make_shared<Texture>(CreateTextureDataFromBmp24(fileName, zipFile));
|
||
textureMap[key] = tex;
|
||
return tex;
|
||
}
|
||
|
||
std::shared_ptr<Texture> TextureManager::LoadFromBmp32(const std::string& fileName, const std::string& zipFile)
|
||
{
|
||
std::string key = MakeKey(fileName, zipFile);
|
||
auto it = textureMap.find(key);
|
||
if (it != textureMap.end())
|
||
return it->second;
|
||
auto tex = std::make_shared<Texture>(CreateTextureDataFromBmp32(fileName, zipFile));
|
||
textureMap[key] = tex;
|
||
return tex;
|
||
}
|
||
|
||
#ifdef PNG_ENABLED
|
||
std::shared_ptr<Texture> TextureManager::LoadFromPng(const std::string& fileName, const std::string& zipFile)
|
||
{
|
||
std::string key = MakeKey(fileName, zipFile);
|
||
auto it = textureMap.find(key);
|
||
if (it != textureMap.end())
|
||
return it->second;
|
||
auto tex = std::make_shared<Texture>(CreateTextureDataFromPng(fileName, zipFile));
|
||
textureMap[key] = tex;
|
||
return tex;
|
||
}
|
||
#endif
|
||
|
||
void TextureManager::Unload(const std::string& fileName, const std::string& zipFile)
|
||
{
|
||
textureMap.erase(MakeKey(fileName, zipFile));
|
||
}
|
||
|
||
void TextureManager::UnloadAll()
|
||
{
|
||
textureMap.clear();
|
||
}
|
||
|
||
} |