space-game001/src/render/TextureManager.cpp
2026-05-07 19:49:41 +03:00

470 lines
13 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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();
}
}