mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-17 17:59:19 +02:00
Two things about the map behind the plan, neither of them the plan's own doing. stb keeps one flip flag for the whole program - there is a single stb_image.c in the build - and the model texture loader turned it on for its own sake, because models want the bottom row first. A WMS tile is the other way about: its first row is its northern edge, which is what the drawing code takes v=0 to be. So every tile came out upside down as soon as any model texture had been loaded through that path, which is why this has come and gone with the order things load in. Both decoders now say which way they want it per thread, where nobody else can reach it. And libcurl is no longer required to configure. A tree without it builds an editor that draws and exports as usual, only with nothing behind the plan, and says so once rather than showing a silent grey screen; the CI installs it, so what comes out of the build has its imagery.
1526 lines
50 KiB
C++
1526 lines
50 KiB
C++
module;
|
|
#include <deque>
|
|
#include <limits>
|
|
#include <mutex>
|
|
#include <ostream>
|
|
#include <sstream>
|
|
#include <type_traits>
|
|
#include <atomic>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include "glad/glad.h"
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <chrono>
|
|
#include <cstring>
|
|
#include <fstream>
|
|
#include <ios>
|
|
#include <istream>
|
|
#include <iterator>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <utility>
|
|
#include <vector>
|
|
#include "global_include/interfaces/ITexture_macros.h"
|
|
#include "utilities/Globals_macros.h"
|
|
#include "utilities/utilities_macros.h"
|
|
#include "stb/stb_image.h"
|
|
#include "dds-ktx/dds-ktx.h"
|
|
#include "winheaders.h"
|
|
|
|
module eu07.model.texture;
|
|
import eu07.application.application;
|
|
import eu07.glm;
|
|
import eu07.simcore;
|
|
import eu07.scripting.pyint;
|
|
import eu07.utilities.dictionary;
|
|
import eu07.utilities.globals;
|
|
import eu07.utilities.logs;
|
|
import eu07.utilities.utilities;
|
|
import eu07.scene.sn_utils;
|
|
import eu07.rendering.flip_s3tc;
|
|
/*
|
|
This Source Code Form is subject to the
|
|
terms of the Mozilla Public License, v.
|
|
2.0. If a copy of the MPL was not
|
|
distributed with this file, You can
|
|
obtain one at
|
|
http://mozilla.org/MPL/2.0/.
|
|
*/
|
|
|
|
/*
|
|
MaSzyna EU07 locomotive simulator
|
|
Copyright (C) 2001-2004 Marcin Wozniak and others
|
|
|
|
*/
|
|
|
|
|
|
//#include <png.h>
|
|
|
|
#define EU07_DEFERRED_TEXTURE_UPLOAD
|
|
|
|
std::array<GLuint, gl::MAX_TEXTURES + gl::HELPER_TEXTURES> opengl_texture::units = { 0 };
|
|
GLint opengl_texture::m_activeunit = -1;
|
|
|
|
std::unordered_map<GLint, int> opengl_texture::precompressed_formats =
|
|
{
|
|
{ GL_COMPRESSED_RGBA8_ETC2_EAC, 16 },
|
|
{ GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC, 16 },
|
|
{ GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT, 8 },
|
|
{ GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT, 16 },
|
|
{ GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT, 16 },
|
|
{ GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, 8 },
|
|
{ GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, 16 },
|
|
{ GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, 16 },
|
|
};
|
|
|
|
std::unordered_map<GLint, GLint> opengl_texture::drivercompressed_formats =
|
|
{
|
|
{ GL_SRGB8_ALPHA8, GL_COMPRESSED_SRGB_ALPHA },
|
|
{ GL_SRGB8, GL_COMPRESSED_SRGB },
|
|
{ GL_RGBA8, GL_COMPRESSED_RGBA },
|
|
{ GL_RGB8, GL_COMPRESSED_RGB },
|
|
{ GL_RG8, GL_COMPRESSED_RG },
|
|
{ GL_R8, GL_COMPRESSED_RED },
|
|
};
|
|
|
|
std::unordered_map<GLint, std::unordered_map<GLint, GLint>> opengl_texture::mapping =
|
|
{
|
|
// image have, material wants, gl internalformat
|
|
{ GL_COMPRESSED_RGBA8_ETC2_EAC , { { GL_SRGB_ALPHA, GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC },
|
|
{ GL_SRGB, GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC },
|
|
{ GL_RGBA, GL_COMPRESSED_RGBA8_ETC2_EAC },
|
|
{ GL_RGB, GL_COMPRESSED_RGBA8_ETC2_EAC },
|
|
{ GL_RG, GL_COMPRESSED_RGBA8_ETC2_EAC },
|
|
{ GL_RED, GL_COMPRESSED_RGBA8_ETC2_EAC } } },
|
|
{ GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, { { GL_SRGB_ALPHA, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT },
|
|
{ GL_SRGB, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT },
|
|
{ GL_RGBA, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT },
|
|
{ GL_RGB, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT },
|
|
{ GL_RG, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT },
|
|
{ GL_RED, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT } } },
|
|
{ GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, { { GL_SRGB_ALPHA, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT },
|
|
{ GL_SRGB, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT },
|
|
{ GL_RGBA, GL_COMPRESSED_RGBA_S3TC_DXT3_EXT },
|
|
{ GL_RGB, GL_COMPRESSED_RGBA_S3TC_DXT3_EXT },
|
|
{ GL_RG, GL_COMPRESSED_RGBA_S3TC_DXT3_EXT },
|
|
{ GL_RED, GL_COMPRESSED_RGBA_S3TC_DXT3_EXT } } },
|
|
{ GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, { { GL_SRGB_ALPHA, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT },
|
|
{ GL_SRGB, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT },
|
|
{ GL_RGBA, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT },
|
|
{ GL_RGB, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT },
|
|
{ GL_RG, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT },
|
|
{ GL_RED, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT } } },
|
|
{ GL_RGBA, { { GL_SRGB_ALPHA, GL_SRGB8_ALPHA8 },
|
|
{ GL_SRGB, GL_SRGB8 },
|
|
{ GL_RGBA, GL_RGBA8 },
|
|
{ GL_RGB, GL_RGB8 },
|
|
{ GL_RG, GL_RG8 },
|
|
{ GL_RED, GL_R8 } } },
|
|
{ GL_RGB, { { GL_SRGB_ALPHA, GL_SRGB8 }, // bad
|
|
{ GL_SRGB, GL_SRGB8 },
|
|
{ GL_RGBA, GL_RGB8 }, // bad
|
|
{ GL_RGB, GL_RGB8 },
|
|
{ GL_RG, GL_RG8 },
|
|
{ GL_RED, GL_R8 } } },
|
|
{ GL_RG, { { GL_SRGB_ALPHA, GL_SRGB8 }, // bad
|
|
{ GL_SRGB, GL_SRGB8 }, // bad
|
|
{ GL_RGBA, GL_RG8 }, // bad
|
|
{ GL_RGB, GL_RG8 }, // bad
|
|
{ GL_RG, GL_RG8 },
|
|
{ GL_RED, GL_R8 } } },
|
|
{ GL_RED, { { GL_SRGB_ALPHA, GL_SRGB8 }, // bad
|
|
{ GL_SRGB, GL_SRGB8 }, // bad
|
|
{ GL_RGBA, GL_R8 }, // bad
|
|
{ GL_RGB, GL_R8 }, // bad
|
|
{ GL_RG, GL_R8 }, // bad
|
|
{ GL_RED, GL_R8 } } },
|
|
};
|
|
|
|
texture_manager::texture_manager() {
|
|
|
|
// since index 0 is used to indicate no texture, we put a blank entry in the first texture slot
|
|
m_textures.emplace_back( new opengl_texture(), std::chrono::steady_clock::time_point() );
|
|
}
|
|
|
|
// convert image to format suitable for given internalformat
|
|
// required for GLES, on desktop GL it will be done by driver
|
|
void opengl_texture::gles_match_internalformat(GLuint internalformat)
|
|
{
|
|
// ignore compressed formats (and hope that GLES driver will support it)
|
|
if (precompressed_formats.find(internalformat) != precompressed_formats.end())
|
|
return;
|
|
|
|
// don't care about sRGB here
|
|
if (internalformat == GL_SRGB8)
|
|
internalformat = GL_RGB8;
|
|
if (internalformat == GL_SRGB8_ALPHA8)
|
|
internalformat = GL_RGBA8;
|
|
|
|
// we don't want BGR(A), reverse it
|
|
if (data_format == GL_BGR)
|
|
{
|
|
std::vector<unsigned char> reverse;
|
|
reverse.resize(data.size());
|
|
|
|
for (int y = 0; y < data_height; y++)
|
|
for (int x = 0; x < data_width; x++)
|
|
{
|
|
int offset = (y * data_width + x) * 3;
|
|
reverse[offset + 0] = data[offset + 2];
|
|
reverse[offset + 1] = data[offset + 1];
|
|
reverse[offset + 2] = data[offset + 0];
|
|
}
|
|
|
|
data_format = GL_RGB;
|
|
data = reverse;
|
|
}
|
|
else if (data_format == GL_BGRA)
|
|
{
|
|
std::vector<unsigned char> reverse;
|
|
reverse.resize(data.size());
|
|
|
|
for (int y = 0; y < data_height; y++)
|
|
for (int x = 0; x < data_width; x++)
|
|
{
|
|
int offset = (y * data_width + x) * 4;
|
|
reverse[offset + 0] = data[offset + 2];
|
|
reverse[offset + 1] = data[offset + 1];
|
|
reverse[offset + 2] = data[offset + 0];
|
|
reverse[offset + 3] = data[offset + 3];
|
|
}
|
|
|
|
data_format = GL_RGBA;
|
|
data = reverse;
|
|
}
|
|
|
|
// if format matches, we're done
|
|
if (data_format == GL_RGBA && internalformat == GL_RGBA8)
|
|
return;
|
|
if (data_format == GL_RGB && internalformat == GL_RGB8)
|
|
return;
|
|
if (data_format == GL_RG && internalformat == GL_RG8)
|
|
return;
|
|
if (data_format == GL_RED && internalformat == GL_R8)
|
|
return;
|
|
|
|
// do conversion
|
|
|
|
int in_c = 0;
|
|
if (data_format == GL_RGBA)
|
|
in_c = 4;
|
|
else if (data_format == GL_RGB)
|
|
in_c = 3;
|
|
else if (data_format == GL_RG)
|
|
in_c = 2;
|
|
else if (data_format == GL_RED)
|
|
in_c = 1;
|
|
|
|
int out_c = 0;
|
|
if (internalformat == GL_RGBA8)
|
|
out_c = 4;
|
|
else if (internalformat == GL_RGB8)
|
|
out_c = 3;
|
|
else if (internalformat == GL_RG8)
|
|
out_c = 2;
|
|
else if (internalformat == GL_R8)
|
|
out_c = 1;
|
|
|
|
if (!in_c || !out_c)
|
|
return; // conversion not supported
|
|
|
|
std::vector<unsigned char> out;
|
|
out.resize(data_width * data_height * out_c);
|
|
|
|
for (int y = 0; y < data_height; y++)
|
|
for (int x = 0; x < data_width; x++)
|
|
{
|
|
int pixel = y * data_width + x;
|
|
int in_off = pixel * in_c;
|
|
int out_off = pixel * out_c;
|
|
for (int i = 0; i < out_c; i++)
|
|
{
|
|
if (i < in_c)
|
|
out[out_off + i] = data[in_off + i];
|
|
else
|
|
out[out_off + i] = 0xFF;
|
|
}
|
|
}
|
|
|
|
if (out_c == 4)
|
|
data_format = GL_RGBA;
|
|
else if (out_c == 3)
|
|
data_format = GL_RGB;
|
|
else if (out_c == 2)
|
|
data_format = GL_RG;
|
|
else if (out_c == 1)
|
|
data_format = GL_RED;
|
|
|
|
data = out;
|
|
}
|
|
|
|
// loads texture data from specified file
|
|
// TODO: wrap it in a workitem class, for the job system deferred loading
|
|
void
|
|
opengl_texture::load() {
|
|
if (data_state == resource_state::good)
|
|
return;
|
|
|
|
if( type == "make:" || type == "internalsrc:" ) {
|
|
// for generated texture we delay data creation until texture is bound
|
|
// this ensures the script will receive all simulation data it might potentially want
|
|
// as any binding will happen after simulation is loaded, initialized and running
|
|
// until then we supply data for a tiny 2x2 grey stub
|
|
make_stub();
|
|
}
|
|
else {
|
|
|
|
WriteLog( "Loading texture data from \"" + name + "\"", logtype::texture );
|
|
|
|
data_state = resource_state::loading;
|
|
|
|
if( type == ".dds" ) { load_DDS(); }
|
|
else if( type == ".tga" ) { load_TGA(); }
|
|
else if( type == ".png" ) { load_STBI(); }
|
|
else if( type == ".ktx" ) { load_KTX(); }
|
|
else if( type == ".bmp" ) { load_STBI(); }
|
|
else if( type == ".jpg" ) { load_STBI(); }
|
|
else if( type == ".tex" ) { load_TEX(); }
|
|
else { goto fail; }
|
|
}
|
|
|
|
// data state will be set by called loader, so we're all done here
|
|
if( data_state == resource_state::good ) {
|
|
|
|
// verify texture size
|
|
if( clamp_power_of_two(data_width) != data_width || clamp_power_of_two(data_height) != data_height ) {
|
|
if( name != "logo" ) {
|
|
WriteLog( "Warning: dimensions of texture \"" + name + "\" aren't powers of 2", logtype::texture );
|
|
}
|
|
}
|
|
if( quantize(data_width, 4u) != data_width || quantize(data_height, 4u) != data_height ) {
|
|
WriteLog( "Warning: dimensions of texture \"" + name + "\" aren't multiples of 4", logtype::texture );
|
|
}
|
|
|
|
has_alpha = data_components == GL_RGBA ? true : false;
|
|
|
|
size = data.size() / 1024;
|
|
|
|
return;
|
|
}
|
|
|
|
fail:
|
|
data_state = resource_state::failed;
|
|
ErrorLog( "Bad texture: failed to load texture \"" + name + "\"" );
|
|
// NOTE: temporary workaround for texture assignment errors
|
|
id = 0;
|
|
return;
|
|
}
|
|
|
|
void opengl_texture::load_PNG()
|
|
{
|
|
//png_image png;
|
|
//memset(&png, 0, sizeof(png_image));
|
|
//png.version = PNG_IMAGE_VERSION;
|
|
//
|
|
//png_image_begin_read_from_file(&png, (name + type).c_str());
|
|
//if (png.warning_or_error)
|
|
//{
|
|
// data_state = resource_state::failed;
|
|
// ErrorLog(name + " error: " + std::string(png.message));
|
|
// return;
|
|
//}
|
|
//
|
|
//if (png.format & PNG_FORMAT_FLAG_ALPHA)
|
|
//{
|
|
// data_format = GL_RGBA;
|
|
// data_components = GL_RGBA;
|
|
// png.format = PNG_FORMAT_RGBA;
|
|
//}
|
|
//else
|
|
//{
|
|
// data_format = GL_RGB;
|
|
// data_components = GL_RGB;
|
|
// png.format = PNG_FORMAT_RGB;
|
|
//}
|
|
//data_width = png.width;
|
|
//data_height = png.height;
|
|
//
|
|
//data.resize(PNG_IMAGE_SIZE(png));
|
|
//
|
|
//png_image_finish_read(&png, nullptr,
|
|
// (void*)&data[0], -data_width * PNG_IMAGE_PIXEL_SIZE(png.format), nullptr);
|
|
//// we're storing texture data internally with bottom-left origin
|
|
//// so use negative stride
|
|
//
|
|
//if (png.warning_or_error)
|
|
//{
|
|
// data_state = resource_state::failed;
|
|
// ErrorLog(name + " error: " + std::string(png.message));
|
|
// return;
|
|
//}
|
|
//
|
|
//data_mapcount = 1;
|
|
//data_state = resource_state::good;
|
|
}
|
|
|
|
void opengl_texture::load_STBI()
|
|
{
|
|
int x, y, n;
|
|
// per thread, not for the whole program: there is one stb_image.c in the build, so the global
|
|
// flag reaches every other decoder too - which is how the editor's WMS tiles ended up upside
|
|
// down as soon as any model texture had been loaded through this path
|
|
stbi_set_flip_vertically_on_load_thread(1);
|
|
uint8_t *image = stbi_load((name + type).c_str(), &x, &y, &n, 4);
|
|
|
|
if (!image) {
|
|
data_state = resource_state::failed;
|
|
ErrorLog(std::string(stbi_failure_reason()));
|
|
return;
|
|
}
|
|
|
|
data.resize(x * y * 4);
|
|
memcpy(&data[0], image, data.size());
|
|
free(image);
|
|
|
|
data_format = GL_RGBA;
|
|
data_components = n == 4 ? GL_RGBA : GL_RGB;
|
|
data_width = x;
|
|
data_height = y;
|
|
data_mapcount = 1;
|
|
data_state = resource_state::good;
|
|
}
|
|
|
|
void
|
|
opengl_texture::make_stub()
|
|
{
|
|
data_width = 2;
|
|
data_height = 2;
|
|
data.resize( data_width * data_height * 3 );
|
|
std::fill( std::begin( data ), std::end( data ), static_cast<char>( 0xc0 ) );
|
|
data_mapcount = 1;
|
|
data_format = GL_RGB;
|
|
data_components = GL_RGB;
|
|
data_state = resource_state::good;
|
|
|
|
is_texstub = true;
|
|
}
|
|
|
|
void
|
|
opengl_texture::make_from_memory(size_t width, size_t height, const uint8_t *raw)
|
|
{
|
|
release();
|
|
|
|
data_width = width;
|
|
data_height = height;
|
|
data.resize(data_width * data_height * 4);
|
|
memcpy(data.data(), raw, data.size());
|
|
|
|
data_format = GL_RGBA;
|
|
data_components = GL_RGBA;
|
|
data_mapcount = 1;
|
|
data_state = resource_state::good;
|
|
|
|
is_texstub = false;
|
|
}
|
|
|
|
void opengl_texture::update_from_memory(size_t width, size_t height, const uint8_t *raw)
|
|
{
|
|
if (id != -1 && (width != data_width || height != data_height || GL_SRGB8_ALPHA8 != data_format || GL_RGBA != data_components))
|
|
{
|
|
glDeleteTextures(1, &id);
|
|
id = -1;
|
|
}
|
|
if (id == -1)
|
|
{
|
|
data_width = width;
|
|
data_height = height;
|
|
data_format = GL_SRGB8_ALPHA8;
|
|
data_components = GL_RGBA;
|
|
glGenTextures(1, &id);
|
|
glBindTexture(target, id);
|
|
set_filtering();
|
|
glTexParameteri(target, GL_TEXTURE_WRAP_S, wrap_mode_s);
|
|
glTexParameteri(target, GL_TEXTURE_WRAP_T, wrap_mode_t);
|
|
if (!glGenerateMipmap)
|
|
glTexParameteri(target, GL_GENERATE_MIPMAP, GL_TRUE);
|
|
}
|
|
else
|
|
{
|
|
glBindTexture(target, id);
|
|
}
|
|
glTexImage2D(target, 0, data_format, data_width, data_height, 0, data_components, GL_UNSIGNED_BYTE, raw);
|
|
if (glGenerateMipmap)
|
|
glGenerateMipmap(target);
|
|
glFlush();
|
|
}
|
|
|
|
void
|
|
opengl_texture::make_request() {
|
|
|
|
auto const components { Split( name, '?' ) };
|
|
|
|
auto dictionary = std::make_shared<dictionary_source>( components.back() );
|
|
|
|
auto rt = std::make_shared<python_rt>();
|
|
rt->shared_tex = this;
|
|
|
|
Application.request( { ToLower( components.front() ), dictionary, rt } );
|
|
}
|
|
|
|
DDCOLORKEY opengl_texture::deserialize_ddck(std::istream &s)
|
|
{
|
|
DDCOLORKEY ddck;
|
|
|
|
ddck.dwColorSpaceLowValue = sn_utils::ld_uint32(s);
|
|
ddck.dwColorSpaceHighValue = sn_utils::ld_uint32(s);
|
|
|
|
return ddck;
|
|
}
|
|
|
|
DDPIXELFORMAT opengl_texture::deserialize_ddpf(std::istream &s)
|
|
{
|
|
DDPIXELFORMAT ddpf;
|
|
|
|
ddpf.dwSize = sn_utils::ld_uint32(s);
|
|
ddpf.dwFlags = sn_utils::ld_uint32(s);
|
|
ddpf.dwFourCC = sn_utils::ld_uint32(s);
|
|
ddpf.dwRGBBitCount = sn_utils::ld_uint32(s);
|
|
ddpf.dwRBitMask = sn_utils::ld_uint32(s);
|
|
ddpf.dwGBitMask = sn_utils::ld_uint32(s);
|
|
ddpf.dwBBitMask = sn_utils::ld_uint32(s);
|
|
ddpf.dwRGBAlphaBitMask = sn_utils::ld_uint32(s);
|
|
|
|
return ddpf;
|
|
}
|
|
|
|
DDSCAPS2 opengl_texture::deserialize_ddscaps(std::istream &s)
|
|
{
|
|
DDSCAPS2 ddsc;
|
|
|
|
ddsc.dwCaps = sn_utils::ld_uint32(s);
|
|
ddsc.dwCaps2 = sn_utils::ld_uint32(s);
|
|
ddsc.dwCaps3 = sn_utils::ld_uint32(s);
|
|
ddsc.dwCaps4 = sn_utils::ld_uint32(s);
|
|
|
|
return ddsc;
|
|
}
|
|
|
|
DDSURFACEDESC2 opengl_texture::deserialize_ddsd(std::istream &s)
|
|
{
|
|
DDSURFACEDESC2 ddsd;
|
|
|
|
ddsd.dwSize = sn_utils::ld_uint32(s);
|
|
ddsd.dwFlags = sn_utils::ld_uint32(s);
|
|
ddsd.dwHeight = sn_utils::ld_uint32(s);
|
|
ddsd.dwWidth = sn_utils::ld_uint32(s);
|
|
ddsd.lPitch = sn_utils::ld_uint32(s);
|
|
ddsd.dwBackBufferCount = sn_utils::ld_uint32(s);
|
|
ddsd.dwMipMapCount = sn_utils::ld_uint32(s);
|
|
ddsd.dwAlphaBitDepth = sn_utils::ld_uint32(s);
|
|
ddsd.dwReserved = sn_utils::ld_uint32(s);
|
|
sn_utils::ld_uint32(s);
|
|
ddsd.lpSurface = nullptr;
|
|
ddsd.ddckCKDestOverlay = deserialize_ddck(s);
|
|
ddsd.ddckCKDestBlt = deserialize_ddck(s);
|
|
ddsd.ddckCKSrcOverlay = deserialize_ddck(s);
|
|
ddsd.ddckCKSrcBlt = deserialize_ddck(s);
|
|
ddsd.ddpfPixelFormat = deserialize_ddpf(s);
|
|
ddsd.ddsCaps = deserialize_ddscaps(s);
|
|
ddsd.dwTextureStage = sn_utils::ld_uint32(s);
|
|
|
|
return ddsd;
|
|
}
|
|
|
|
void
|
|
opengl_texture::load_DDS() {
|
|
|
|
std::ifstream file( name + type, std::ios::binary | std::ios::ate ); file.unsetf( std::ios::skipws );
|
|
std::size_t filesize = static_cast<size_t>(file.tellg()); // ios::ate already positioned us at the end of the file
|
|
file.seekg( 0, std::ios::beg ); // rewind the caret afterwards
|
|
|
|
char filecode[5];
|
|
file.read(filecode, 4);
|
|
filesize -= 4;
|
|
filecode[4] = 0;
|
|
|
|
if( filecode != std::string( "DDS " ) )
|
|
{
|
|
data_state = resource_state::failed;
|
|
return;
|
|
}
|
|
|
|
DDSURFACEDESC2 ddsd = deserialize_ddsd(file);
|
|
filesize -= 124;
|
|
|
|
//
|
|
// This .dds loader supports the loading of compressed formats DXT1, DXT3
|
|
// and DXT5.
|
|
//
|
|
|
|
switch (ddsd.ddpfPixelFormat.dwFourCC)
|
|
{
|
|
case FOURCC_DXT1:
|
|
// DXT1's compression ratio is 8:1
|
|
data_format = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
|
|
break;
|
|
|
|
case FOURCC_DXT3:
|
|
// DXT3's compression ratio is 4:1
|
|
data_format = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
|
|
break;
|
|
|
|
case FOURCC_DXT5:
|
|
// DXT5's compression ratio is 4:1
|
|
data_format = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
|
|
break;
|
|
|
|
default:
|
|
data_state = resource_state::failed;
|
|
return;
|
|
}
|
|
|
|
data_width = ddsd.dwWidth;
|
|
data_height = ddsd.dwHeight;
|
|
data_mapcount = ddsd.dwMipMapCount;
|
|
|
|
int blockSize = data_format == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT ? 8 : 16;
|
|
int offset = 0;
|
|
|
|
while( data_width > Global.CurrentMaxTextureSize || data_height > Global.CurrentMaxTextureSize ) {
|
|
// pomijanie zbyt dużych mipmap, jeśli wymagane jest ograniczenie rozmiaru
|
|
offset += (data_width + 3) / 4 * ( ( data_height + 3 ) / 4 ) * blockSize;
|
|
data_width /= 2;
|
|
data_height /= 2;
|
|
--data_mapcount;
|
|
WriteLog( "Texture pixelcount exceeds specified limits, skipping mipmap level" );
|
|
};
|
|
|
|
if( data_mapcount <= 0 ) {
|
|
// there's a chance we've discarded the provided mipmap(s) as too large
|
|
WriteLog( "Texture \"" + name + "\" has no mipmaps which can fit currently set texture pixelcount limits." );
|
|
data_state = resource_state::failed;
|
|
return;
|
|
}
|
|
|
|
size_t datasize = filesize - offset;
|
|
|
|
if( datasize == 0 ) {
|
|
// catch malformed .dds files
|
|
WriteLog( "Bad texture: file \"" + name + "\" is malformed and holds no texture data.", logtype::texture );
|
|
data_state = resource_state::failed;
|
|
return;
|
|
}
|
|
// reserve space and load texture data
|
|
data.resize( datasize );
|
|
|
|
if( offset != 0 ) {
|
|
// skip data for mipmaps we don't need
|
|
file.seekg( offset, std::ios_base::cur );
|
|
filesize -= offset;
|
|
}
|
|
file.read((char *)&data[0], datasize);
|
|
filesize -= datasize;
|
|
|
|
// we're storing texture data internally with bottom-left origin,
|
|
// while DDS stores it with top-left origin. we need to flip it.
|
|
if (Global.dds_upper_origin)
|
|
{
|
|
char *mipmap = (char*)&data[0];
|
|
int mapcount = data_mapcount,
|
|
width = data_width,
|
|
height = data_height;
|
|
while (mapcount)
|
|
{
|
|
if (ddsd.ddpfPixelFormat.dwFourCC == FOURCC_DXT1)
|
|
flip_s3tc::flip_dxt1_image(mipmap, width, height);
|
|
else if (ddsd.ddpfPixelFormat.dwFourCC == FOURCC_DXT3)
|
|
flip_s3tc::flip_dxt23_image(mipmap, width, height);
|
|
else if (ddsd.ddpfPixelFormat.dwFourCC == FOURCC_DXT5)
|
|
flip_s3tc::flip_dxt45_image(mipmap, width, height);
|
|
|
|
mipmap += (width + 3) / 4 * ( ( height + 3 ) / 4 ) * blockSize;
|
|
width = std::max( width / 2, 4 );
|
|
height = std::max( height / 2, 4 );
|
|
--mapcount;
|
|
}
|
|
}
|
|
|
|
data_components =
|
|
ddsd.ddpfPixelFormat.dwFourCC == FOURCC_DXT1 ? GL_RGB : GL_RGBA;
|
|
|
|
data_state = resource_state::good;
|
|
|
|
return;
|
|
}
|
|
|
|
void
|
|
opengl_texture::load_KTX() {
|
|
std::ifstream file( name + type, std::ios::binary | std::ios::ate ); file.unsetf( std::ios::skipws );
|
|
std::size_t filesize = static_cast<size_t>(file.tellg()); // ios::ate already positioned us at the end of the file
|
|
file.seekg( 0, std::ios::beg ); // rewind the caret afterwards
|
|
|
|
std::vector<char> filecontent;
|
|
filecontent.resize(filesize);
|
|
file.read(filecontent.data(), filecontent.size());
|
|
|
|
ddsktx_texture_info info;
|
|
if (!ddsktx_parse(&info, filecontent.data(), filecontent.size(), nullptr)) {
|
|
ErrorLog("Bad texture: KTX parsing failed", logtype::texture);
|
|
data_state = resource_state::failed;
|
|
return;
|
|
}
|
|
|
|
if (info.format != DDSKTX_FORMAT_ETC2A) {
|
|
ErrorLog("Bad texture: currently unsupported KTX type", logtype::texture);
|
|
data_state = resource_state::failed;
|
|
return;
|
|
}
|
|
|
|
data_format = GL_COMPRESSED_RGBA8_ETC2_EAC;
|
|
data_components = GL_RGBA;
|
|
data_mapcount = info.num_mips;
|
|
|
|
bool started = false;
|
|
|
|
for (int level = 0; level < info.num_mips; level++) {
|
|
ddsktx_sub_data sub_data;
|
|
ddsktx_get_sub(&info, &sub_data, filecontent.data(), filecontent.size(), 0, 0, level);
|
|
|
|
if (!started) {
|
|
data_width = sub_data.width;
|
|
data_height = sub_data.height;
|
|
|
|
if( data_width > Global.CurrentMaxTextureSize || data_height > Global.CurrentMaxTextureSize ) {
|
|
data_mapcount--;
|
|
continue;
|
|
}
|
|
|
|
started = true;
|
|
}
|
|
|
|
size_t data_offset = data.size();
|
|
data.resize(data.size() + sub_data.size_bytes);
|
|
memcpy(data.data() + data_offset, sub_data.buff, sub_data.size_bytes);
|
|
}
|
|
|
|
if (!data_mapcount) {
|
|
// there's a chance we've discarded the provided mipmap(s) as too large
|
|
WriteLog( "Texture \"" + name + "\" has no mipmaps which can fit currently set texture pixelcount limits." );
|
|
data_state = resource_state::failed;
|
|
return;
|
|
}
|
|
|
|
data_state = resource_state::good;
|
|
}
|
|
|
|
void
|
|
opengl_texture::load_TEX() {
|
|
|
|
std::ifstream file( name + type, std::ios::binary ); file.unsetf( std::ios::skipws );
|
|
|
|
char head[ 5 ];
|
|
file.read( head, 4 );
|
|
head[ 4 ] = 0;
|
|
|
|
bool hasalpha;
|
|
if( std::string( "RGB " ) == head ) {
|
|
hasalpha = false;
|
|
}
|
|
else if( std::string( "RGBA" ) == head ) {
|
|
hasalpha = true;
|
|
}
|
|
else {
|
|
ErrorLog( "Bad texture: unrecognized TEX texture sub-format: " + std::string(head), logtype::texture );
|
|
data_state = resource_state::failed;
|
|
return;
|
|
};
|
|
|
|
file.read( (char *)&data_width, sizeof( int ) );
|
|
file.read( (char *)&data_height, sizeof( int ) );
|
|
|
|
std::size_t datasize = data_width * data_height * ( hasalpha ? 4 : 3 );
|
|
|
|
data.resize( datasize );
|
|
file.read( reinterpret_cast<char *>( &data[0] ), datasize );
|
|
|
|
// fill remaining data info
|
|
if( true == hasalpha ) {
|
|
|
|
data_format = GL_RGBA;
|
|
data_components = GL_RGBA;
|
|
}
|
|
else {
|
|
|
|
data_format = GL_RGB;
|
|
data_components = GL_RGB;
|
|
}
|
|
data_mapcount = 1;
|
|
data_state = resource_state::good;
|
|
|
|
return;
|
|
}
|
|
|
|
void
|
|
opengl_texture::load_TGA() {
|
|
|
|
std::ifstream file( name + type, std::ios::binary ); file.unsetf( std::ios::skipws );
|
|
|
|
// Read the header of the TGA, compare it with the known headers for compressed and uncompressed TGAs
|
|
unsigned char tgaheader[ 18 ];
|
|
file.read( (char *)tgaheader, sizeof( unsigned char ) * 18 );
|
|
|
|
while( tgaheader[ 0 ] > 0 ) {
|
|
--tgaheader[ 0 ];
|
|
|
|
unsigned char temp;
|
|
file.read( (char *)&temp, sizeof( unsigned char ) );
|
|
}
|
|
|
|
data_width = tgaheader[ 13 ] * 256 + tgaheader[ 12 ];
|
|
data_height = tgaheader[ 15 ] * 256 + tgaheader[ 14 ];
|
|
int const bytesperpixel = tgaheader[ 16 ] / 8;
|
|
|
|
// check whether width, height an BitsPerPixel are valid
|
|
if( data_width <= 0
|
|
|| data_height <= 0
|
|
|| ( bytesperpixel != 1 && bytesperpixel != 3 && bytesperpixel != 4 ) ) {
|
|
|
|
data_state = resource_state::failed;
|
|
return;
|
|
}
|
|
|
|
// allocate the data buffer
|
|
int const datasize = data_width * data_height * 4;
|
|
data.resize(datasize);
|
|
|
|
// call the appropriate loader-routine
|
|
if( tgaheader[ 2 ] == 2 ) {
|
|
// uncompressed TGA
|
|
if( bytesperpixel == 4 ) {
|
|
// read the data directly
|
|
file.read( reinterpret_cast<char*>( &data[0] ), datasize );
|
|
}
|
|
else {
|
|
// rgb or greyscale image, expand to bgra
|
|
unsigned char buffer[ 4 ] = { 255, 255, 255, 255 }; // alpha channel will be white
|
|
|
|
unsigned int *datapointer = (unsigned int*)&data[0];
|
|
unsigned int *bufferpointer = (unsigned int*)&buffer[ 0 ];
|
|
|
|
int const pixelcount = data_width * data_height;
|
|
|
|
for( int i = 0; i < pixelcount; ++i ) {
|
|
|
|
file.read( (char *)&buffer[ 0 ], sizeof( unsigned char ) * bytesperpixel );
|
|
if( bytesperpixel == 1 ) {
|
|
// expand greyscale data
|
|
buffer[ 1 ] = buffer[ 0 ];
|
|
buffer[ 2 ] = buffer[ 0 ];
|
|
}
|
|
// copy all four values in one operation
|
|
*datapointer = *bufferpointer;
|
|
++datapointer;
|
|
}
|
|
}
|
|
}
|
|
else if( tgaheader[ 2 ] == 10 ) {
|
|
// compressed TGA
|
|
int currentpixel = 0;
|
|
|
|
unsigned char buffer[ 4 ] = { 255, 255, 255, 255 };
|
|
const int pixelcount = data_width * data_height;
|
|
|
|
unsigned int *datapointer = (unsigned int *)&data[0];
|
|
unsigned int *bufferpointer = (unsigned int *)&buffer[ 0 ];
|
|
|
|
do {
|
|
unsigned char chunkheader = 0;
|
|
|
|
file.read( (char *)&chunkheader, sizeof( unsigned char ) );
|
|
|
|
if( (chunkheader & 0x80 ) == 0 ) {
|
|
// if the high bit is not set, it means it is the number of RAW color packets, plus 1
|
|
for( int i = 0; i <= chunkheader; ++i ) {
|
|
|
|
file.read( (char *)&buffer[ 0 ], bytesperpixel );
|
|
|
|
if( bytesperpixel == 1 ) {
|
|
// expand greyscale data
|
|
buffer[ 1 ] = buffer[ 0 ];
|
|
buffer[ 2 ] = buffer[ 0 ];
|
|
}
|
|
// copy all four values in one operation
|
|
*datapointer = *bufferpointer;
|
|
|
|
++datapointer;
|
|
++currentpixel;
|
|
}
|
|
}
|
|
else {
|
|
// rle chunk, the color supplied afterwards is reapeated header + 1 times (not including the highest bit)
|
|
chunkheader &= ~0x80;
|
|
// read the current color
|
|
file.read( (char *)&buffer[ 0 ], bytesperpixel );
|
|
|
|
if( bytesperpixel == 1 ) {
|
|
// expand greyscale data
|
|
buffer[ 1 ] = buffer[ 0 ];
|
|
buffer[ 2 ] = buffer[ 0 ];
|
|
}
|
|
// copy the color into the image data as many times as dictated
|
|
for( int i = 0; i <= chunkheader; ++i ) {
|
|
|
|
*datapointer = *bufferpointer;
|
|
++datapointer;
|
|
++currentpixel;
|
|
}
|
|
}
|
|
|
|
} while( currentpixel < pixelcount );
|
|
}
|
|
else {
|
|
// unrecognized TGA sub-type
|
|
data_state = resource_state::failed;
|
|
return;
|
|
}
|
|
|
|
if( ( tgaheader[ 17 ] & 0x20 ) != 0 ) {
|
|
// normally origin is bottom-left
|
|
// if byte 17 bit 5 is set, it is top-left and needs flip
|
|
flip_vertical();
|
|
}
|
|
|
|
downsize( GL_BGRA );
|
|
if( data_width > Global.CurrentMaxTextureSize || data_height > Global.CurrentMaxTextureSize ) {
|
|
// for non-square textures there's currently possibility the scaling routine will have to abort
|
|
// before it gets all work done
|
|
data_state = resource_state::failed;
|
|
return;
|
|
}
|
|
|
|
// TODO: add horizontal/vertical data flip, based on the descriptor (18th) header byte
|
|
|
|
// fill remaining data info
|
|
data_mapcount = 1;
|
|
data_format = GL_BGRA;
|
|
data_components =
|
|
bytesperpixel == 4 ? GL_RGBA : GL_RGB;
|
|
data_state = resource_state::good;
|
|
|
|
return;
|
|
}
|
|
|
|
bool
|
|
opengl_texture::bind(size_t unit) {
|
|
|
|
if( false == is_ready
|
|
&& false == create() ) {
|
|
return false;
|
|
}
|
|
|
|
if (units[unit] == id)
|
|
return true;
|
|
if (GLAD_GL_ARB_direct_state_access)
|
|
{
|
|
glBindTextureUnit(unit, id);
|
|
}
|
|
else
|
|
{
|
|
if (unit != m_activeunit)
|
|
{
|
|
glActiveTexture(GL_TEXTURE0 + unit);
|
|
m_activeunit = unit;
|
|
}
|
|
glBindTexture(target, id);
|
|
}
|
|
units[unit] = id;
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
opengl_texture::unbind(size_t unit)
|
|
{
|
|
if (GLAD_GL_ARB_direct_state_access)
|
|
{
|
|
glBindTextureUnit(unit, 0);
|
|
}
|
|
else
|
|
{
|
|
if (unit != m_activeunit)
|
|
{
|
|
glActiveTexture(GL_TEXTURE0 + unit);
|
|
m_activeunit = unit;
|
|
}
|
|
//todo: for other targets
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
}
|
|
units[unit] = 0;
|
|
}
|
|
|
|
bool
|
|
opengl_texture::create( bool const Static ) {
|
|
|
|
if( data_state != resource_state::good && !is_rendertarget ) {
|
|
// don't bother until we have useful texture data
|
|
// and it isn't rendertarget texture without loaded data
|
|
return false;
|
|
}
|
|
|
|
// TODO: consider creating and storing low-res version of the texture if it's ever unloaded from the gfx card,
|
|
// as a placeholder until it can be loaded again
|
|
if( id == -1 ) {
|
|
|
|
::glGenTextures( 1, &id );
|
|
::glBindTexture( target, id );
|
|
|
|
// analyze specified texture traits
|
|
for( auto const &trait : traits ) {
|
|
|
|
switch( trait ) {
|
|
|
|
case 's': { wrap_mode_s = GL_CLAMP_TO_EDGE; break; }
|
|
case 't': { wrap_mode_t = GL_CLAMP_TO_EDGE; break; }
|
|
}
|
|
}
|
|
|
|
// upload texture data
|
|
int dataoffset = 0,
|
|
datasize = 0,
|
|
datawidth = data_width,
|
|
dataheight = data_height;
|
|
if (is_rendertarget)
|
|
{
|
|
if (data_components == GL_DEPTH_COMPONENT)
|
|
{
|
|
glTexParameteri(target, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
|
|
wrap_mode_s = GL_CLAMP_TO_BORDER;
|
|
wrap_mode_t = GL_CLAMP_TO_BORDER;
|
|
float borderColor[] = { 0.0f, 0.0f, 0.0f, 0.0f };
|
|
glTexParameterfv(target, GL_TEXTURE_BORDER_COLOR, borderColor);
|
|
}
|
|
glTexParameteri( target, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
|
|
glTexParameteri( target, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
|
|
glTexParameteri( target, GL_TEXTURE_WRAP_S, wrap_mode_s );
|
|
glTexParameteri( target, GL_TEXTURE_WRAP_T, wrap_mode_t );
|
|
|
|
if (Global.gfx_usegles)
|
|
{
|
|
if( target == GL_TEXTURE_2D )
|
|
glTexStorage2D(target, std::countr_zero(static_cast<unsigned int>(std::max(data_width, data_height))) + 1, data_format, data_width, data_height);
|
|
else if( target == GL_TEXTURE_2D_MULTISAMPLE )
|
|
glTexStorage2DMultisample( target, samples, data_format, data_width, data_height, GL_FALSE );
|
|
else if( target == GL_TEXTURE_2D_ARRAY )
|
|
glTexStorage3D(target, std::countr_zero(static_cast<unsigned int>(std::max(data_width, data_height))) + 1, data_format, data_width, data_height, layers);
|
|
else if( target == GL_TEXTURE_2D_MULTISAMPLE_ARRAY )
|
|
glTexStorage3DMultisample( target, samples, data_format, data_width, data_height, layers, GL_FALSE );
|
|
}
|
|
else {
|
|
if( target == GL_TEXTURE_2D )
|
|
glTexImage2D( target, 0, data_format, data_width, data_height, 0, data_components, GL_UNSIGNED_SHORT, nullptr );
|
|
else if( target == GL_TEXTURE_2D_MULTISAMPLE )
|
|
glTexImage2DMultisample( target, samples, data_format, data_width, data_height, GL_FALSE );
|
|
else if( target == GL_TEXTURE_2D_ARRAY )
|
|
glTexImage3D( target, 0, data_format, data_width, data_height, layers, 0, data_components, GL_UNSIGNED_SHORT, nullptr );
|
|
else if( target == GL_TEXTURE_2D_MULTISAMPLE_ARRAY )
|
|
glTexImage3DMultisample( target, samples, data_format, data_width, data_height, layers, GL_FALSE );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
::glTexParameteri(target, GL_TEXTURE_WRAP_S, wrap_mode_s);
|
|
::glTexParameteri(target, GL_TEXTURE_WRAP_T, wrap_mode_t);
|
|
set_filtering();
|
|
|
|
// data_format and data_type specifies how image is laid out in memory
|
|
// data_components specifies what useful channels image contains
|
|
// components_hint specifies what format we want to load
|
|
|
|
// now map that mess into opengl internal format
|
|
|
|
GLint components = data_components;
|
|
auto f_it = precompressed_formats.find(data_format);
|
|
if (f_it != precompressed_formats.end())
|
|
components = data_format;
|
|
|
|
if (!components_hint)
|
|
components_hint = GL_SRGB_ALPHA;
|
|
|
|
GLint internal_format = mapping[components][components_hint];
|
|
|
|
if (Global.gfx_usegles)
|
|
{
|
|
// GLES cannot generate mipmaps on SRGB8
|
|
if (internal_format == GL_SRGB8)
|
|
internal_format = GL_SRGB8_ALPHA8;
|
|
|
|
gles_match_internalformat(internal_format);
|
|
}
|
|
|
|
auto blocksize_it = precompressed_formats.find(internal_format);
|
|
|
|
if ( data_mapcount == 1 && !glGenerateMipmap ) {
|
|
glTexParameteri(target, GL_GENERATE_MIPMAP, GL_TRUE);
|
|
}
|
|
|
|
for( int maplevel = 0; maplevel < data_mapcount; ++maplevel ) {
|
|
|
|
if (blocksize_it != precompressed_formats.end())
|
|
{
|
|
// compressed dds formats
|
|
const int datablocksize = blocksize_it->second;
|
|
|
|
datasize = (std::max(datawidth, 4) + 3) / 4 * ( ( std::max( dataheight, 4 ) + 3 ) / 4 ) * datablocksize;
|
|
|
|
::glCompressedTexImage2D(
|
|
target, maplevel, internal_format,
|
|
datawidth, dataheight, 0,
|
|
datasize, (GLubyte *)&data[ dataoffset ] );
|
|
|
|
dataoffset += datasize;
|
|
datawidth = std::max( datawidth / 2, 1 );
|
|
dataheight = std::max( dataheight / 2, 1 );
|
|
}
|
|
else {
|
|
GLint compressed_format = drivercompressed_formats[internal_format];
|
|
|
|
// uncompressed texture data. have the gfx card do the compression as it sees fit
|
|
::glTexImage2D(
|
|
target, 0,
|
|
Global.compress_tex ? compressed_format : internal_format,
|
|
data_width, data_height, 0,
|
|
data_format, data_type, (GLubyte *)&data[ 0 ] );
|
|
}
|
|
}
|
|
|
|
if ( data_mapcount == 1 && glGenerateMipmap ) {
|
|
glGenerateMipmap(target);
|
|
}
|
|
|
|
if( true == Global.ResourceMove
|
|
|| false == Global.ResourceSweep ) {
|
|
// if garbage collection is disabled we don't expect having to upload the texture more than once
|
|
data = std::vector<unsigned char>();
|
|
data_state = resource_state::none;
|
|
}
|
|
}
|
|
|
|
if( type == "make:" ) {
|
|
// for generated textures send a request to have the actual content of the texture generated
|
|
make_request();
|
|
}
|
|
|
|
is_static = Static;
|
|
|
|
is_ready = true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// releases resources allocated on the opengl end, storing local copy if requested
|
|
void
|
|
opengl_texture::release() {
|
|
|
|
if( id == -1 ) { return; }
|
|
if( is_static ) { return; }
|
|
|
|
if( true == Global.ResourceMove && !is_rendertarget ) {
|
|
// if resource move is enabled we don't keep a cpu side copy after upload
|
|
// so need to re-acquire the data before release
|
|
// TBD, TODO: instead of vram-ram transfer fetch the data 'normally' from the disk using worker thread
|
|
if( type == "make:" ) {
|
|
// auto generated textures only store a stub
|
|
make_stub();
|
|
}
|
|
else {
|
|
::glBindTexture(target, id );
|
|
GLint datasize {};
|
|
GLint iscompressed {};
|
|
::glGetTexLevelParameteriv(target, 0, GL_TEXTURE_COMPRESSED, &iscompressed );
|
|
if( iscompressed == GL_TRUE ) {
|
|
// texture is compressed on the gpu side
|
|
// query texture details needed to perform the backup...
|
|
::glGetTexLevelParameteriv(target, 0, GL_TEXTURE_INTERNAL_FORMAT, &data_format );
|
|
::glGetTexLevelParameteriv(target, 0, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &datasize );
|
|
data.resize( datasize );
|
|
// ...fetch the data...
|
|
::glGetCompressedTexImage(target, 0, &data[ 0 ] );
|
|
}
|
|
else {
|
|
// for whatever reason texture didn't get compressed during upload
|
|
// fallback on plain rgba storage...
|
|
data_format = GL_RGBA;
|
|
data_type = GL_UNSIGNED_BYTE;
|
|
data.resize( data_width * data_height * 4 );
|
|
// ...fetch the data...
|
|
::glGetTexImage(target, 0, data_format, GL_UNSIGNED_BYTE, &data[ 0 ] );
|
|
}
|
|
// ...and update texture object state
|
|
data_mapcount = 1; // we keep copy of only top mipmap level
|
|
data_state = resource_state::good;
|
|
}
|
|
}
|
|
// release opengl resources
|
|
::glDeleteTextures( 1, &id );
|
|
id = -1;
|
|
is_ready = false;
|
|
|
|
return;
|
|
}
|
|
|
|
void
|
|
opengl_texture::alloc_rendertarget( GLint format, GLint components, int width, int height, int l, int s, GLint wrap ) {
|
|
|
|
data_width = width;
|
|
data_height = height;
|
|
data_format = format;
|
|
data_components = components;
|
|
data_mapcount = 1;
|
|
is_rendertarget = true;
|
|
wrap_mode_s = wrap;
|
|
wrap_mode_t = wrap;
|
|
samples = s;
|
|
if( Global.gfx_usegles && !glTexStorage2DMultisample ) {
|
|
samples = 1;
|
|
}
|
|
layers = l;
|
|
if( layers > 1 ) {
|
|
target = samples > 1 ? GL_TEXTURE_2D_MULTISAMPLE_ARRAY : GL_TEXTURE_2D_ARRAY;
|
|
}
|
|
else {
|
|
target = samples > 1 ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D;
|
|
}
|
|
create();
|
|
}
|
|
|
|
void
|
|
opengl_texture::set_components_hint( GLint hint ) {
|
|
|
|
components_hint = hint;
|
|
}
|
|
|
|
void
|
|
opengl_texture::reset_unit_cache() {
|
|
|
|
for( auto &unit : units ) {
|
|
unit = 0;
|
|
}
|
|
m_activeunit = -1;
|
|
}
|
|
|
|
void
|
|
opengl_texture::set_filtering() const
|
|
{
|
|
// default texture mode
|
|
::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
|
|
::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
|
|
|
|
if (Global.AnisotropicFiltering >= 0
|
|
&& ( GLAD_GL_EXT_texture_filter_anisotropic || GLAD_GL_ARB_texture_filter_anisotropic) ) {
|
|
// anisotropic filtering
|
|
::glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, Global.AnisotropicFiltering );
|
|
}
|
|
|
|
if( Global.LegacyRenderer ) {
|
|
|
|
bool sharpen{ false };
|
|
for( auto const &trait : traits ) {
|
|
|
|
switch( trait ) {
|
|
|
|
case '#': { sharpen = true; break; }
|
|
default: { break; }
|
|
}
|
|
}
|
|
|
|
if( true == sharpen ) {
|
|
// #: sharpen more
|
|
::glTexEnvf( GL_TEXTURE_FILTER_CONTROL, GL_TEXTURE_LOD_BIAS, -2.0f );
|
|
}
|
|
else {
|
|
// regular texture sharpening
|
|
::glTexEnvf( GL_TEXTURE_FILTER_CONTROL, GL_TEXTURE_LOD_BIAS, -1.0f );
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
opengl_texture::downsize( GLuint const Format ) {
|
|
|
|
while( data_width > Global.CurrentMaxTextureSize || data_height > Global.CurrentMaxTextureSize ) {
|
|
// scale down the base texture, if it's larger than allowed maximum
|
|
// NOTE: scaling is uniform along both axes, meaning non-square textures can drop below the maximum
|
|
// TODO: replace with proper scaling function once we have image middleware in place
|
|
if( data_width < 2 || data_height < 2 ) {
|
|
// can't go any smaller
|
|
break;
|
|
}
|
|
|
|
WriteLog( "Texture pixelcount exceeds specified limits, downsampling data" );
|
|
// trim potential odd texture sizes
|
|
data_width -= data_width % 2;
|
|
data_height -= data_height % 2;
|
|
switch( Format ) {
|
|
|
|
case GL_RGB: { downsample< glm::tvec3<std::uint8_t> >( data_width, data_height, data.data() ); break; }
|
|
case GL_BGRA:
|
|
case GL_RGBA: { downsample< glm::tvec4<std::uint8_t> >( data_width, data_height, data.data() ); break; }
|
|
default: { break; }
|
|
}
|
|
data_width /= 2;
|
|
data_height /= 2;
|
|
data.resize( data.size() / 4 ); // not strictly needed, but, eh
|
|
};
|
|
}
|
|
|
|
void
|
|
opengl_texture::flip_vertical() {
|
|
|
|
auto const swapsize { data_width * 4 };
|
|
auto destination { data.begin() + ( data_height - 1 ) * swapsize };
|
|
auto sampler { data.begin() };
|
|
|
|
for( auto row = 0; row < data_height / 2; ++row ) {
|
|
|
|
std::swap_ranges( sampler, sampler + swapsize, destination );
|
|
sampler += swapsize;
|
|
destination -= swapsize;
|
|
}
|
|
}
|
|
|
|
void
|
|
texture_manager::unit( GLint const Textureunit ) {
|
|
|
|
if( opengl_texture::m_activeunit == Textureunit ) { return; }
|
|
|
|
opengl_texture::m_activeunit = Textureunit;
|
|
::glActiveTexture( GL_TEXTURE0 + Textureunit );
|
|
}
|
|
|
|
// ustalenie numeru tekstury, wczytanie jeśli jeszcze takiej nie było
|
|
texture_handle
|
|
texture_manager::create( std::string Filename, bool const Loadnow, GLint Formathint ) {
|
|
|
|
if( contains( Filename, '|' ) ) {
|
|
Filename.erase( Filename.find( '|' ) ); // po | może być nazwa kolejnej tekstury
|
|
}
|
|
|
|
std::pair<std::string, std::string> locator; // resource name, resource type
|
|
std::string traits;
|
|
|
|
// discern textures generated by a script
|
|
// TBD: support file: for file resources?
|
|
auto const isgenerated { Filename.find( "make:" ) == 0 };
|
|
auto const isinternalsrc { Filename.find( "internal_src:" ) == 0 };
|
|
// process supplied resource name
|
|
if( isgenerated || isinternalsrc ) {
|
|
// generated resource
|
|
// scheme:(user@)path?query
|
|
|
|
// remove scheme indicator
|
|
Filename.erase( 0, Filename.find( ':' ) + 1 );
|
|
// TODO: extract traits specification from the query
|
|
// clean up slashes
|
|
erase_leading_slashes( Filename );
|
|
}
|
|
else {
|
|
// regular file resource
|
|
// (filepath/)(#)filename.extension(:traits)
|
|
|
|
// extract trait specifications
|
|
auto const traitpos = Filename.rfind( ':' );
|
|
if( traitpos != std::string::npos ) {
|
|
// po dwukropku mogą być podane dodatkowe informacje niebędące nazwą tekstury
|
|
if( Filename.size() > traitpos + 1 ) {
|
|
traits = Filename.substr( traitpos + 1 );
|
|
}
|
|
Filename.erase( traitpos );
|
|
}
|
|
// potentially trim file type indicator since we check for multiple types
|
|
erase_extension( Filename );
|
|
// clean up slashes
|
|
erase_leading_slashes( Filename );
|
|
Filename = ToLower( Filename );
|
|
// temporary code for legacy assets -- textures with names beginning with # are to be sharpened
|
|
if( Filename.starts_with("#") || contains( Filename, "/#" ) ) {
|
|
traits += '#';
|
|
}
|
|
}
|
|
// try to locate requested texture in the databank
|
|
// TBD, TODO: include trait specification in the resource id in the databank?
|
|
auto const lookup { find_in_databank( Filename ) };
|
|
if( lookup != npos ) {
|
|
return lookup;
|
|
}
|
|
// if the lookup fails...
|
|
if( isgenerated ) {
|
|
// TODO: verify presence of the generator script
|
|
locator.first = Filename;
|
|
locator.second = "make:";
|
|
}
|
|
else if ( isinternalsrc ) {
|
|
locator.first = Filename;
|
|
locator.second = "internalsrc:";
|
|
}
|
|
else {
|
|
// ...for file resources check if it's on disk
|
|
locator = find_on_disk( Filename );
|
|
if( true == locator.first.empty() ) {
|
|
// there's nothing matching in the databank nor on the disk, report failure
|
|
ErrorLog( "Bad file: failed to locate texture file \"" + Filename + "\"", logtype::file );
|
|
return npos;
|
|
}
|
|
}
|
|
|
|
auto texture = new opengl_texture();
|
|
texture->name = locator.first;
|
|
texture->type = locator.second;
|
|
texture->traits = traits;
|
|
texture->components_hint = Formathint;
|
|
auto const textureindex = (texture_handle)m_textures.size();
|
|
m_textures.emplace_back( texture, std::chrono::steady_clock::time_point() );
|
|
m_texturemappings.emplace( locator.first, textureindex );
|
|
|
|
WriteLog( "Created texture object for \"" + locator.first + "\"", logtype::texture );
|
|
|
|
if( true == Loadnow ) {
|
|
|
|
texture_manager::texture( textureindex ).load();
|
|
#ifndef EU07_DEFERRED_TEXTURE_UPLOAD
|
|
texture_manager::texture( textureindex ).create();
|
|
// texture creation binds a different texture, force a re-bind on next use
|
|
m_activetexture = -1;
|
|
#endif
|
|
}
|
|
|
|
return textureindex;
|
|
};
|
|
|
|
void
|
|
texture_manager::bind( std::size_t const Unit, texture_handle const Texture ) {
|
|
|
|
if( Unit == -1 ) { return; } // no texture unit, nothing to bind the texture to
|
|
|
|
if (Texture != null_handle)
|
|
mark_as_used(Texture).bind(Unit);
|
|
else
|
|
opengl_texture::unbind(Unit);
|
|
}
|
|
|
|
opengl_texture &
|
|
texture_manager::mark_as_used(const texture_handle Texture) {
|
|
|
|
auto &pair = m_textures[ Texture ];
|
|
pair.second = m_garbagecollector.timestamp();
|
|
return *pair.first;
|
|
}
|
|
|
|
void
|
|
texture_manager::delete_textures() {
|
|
for( auto const &texture : m_textures ) {
|
|
// usunięcie wszyskich tekstur (bez usuwania struktury)
|
|
if( texture.first->id > 0
|
|
&& texture.first->id != -1 ) {
|
|
::glDeleteTextures( 1, &texture.first->id );
|
|
}
|
|
delete texture.first;
|
|
}
|
|
}
|
|
|
|
// performs a resource sweep
|
|
void
|
|
texture_manager::update() {
|
|
|
|
if( m_garbagecollector.sweep() > 0 ) {
|
|
for( auto &unit : opengl_texture::units ) {
|
|
unit = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// debug performance string
|
|
std::string
|
|
texture_manager::info() const {
|
|
|
|
// TODO: cache this data and update only during resource sweep
|
|
std::size_t totaltexturecount{ m_textures.size() - 1 };
|
|
std::size_t totaltexturesize{ 0 };
|
|
#ifdef EU07_DEFERRED_TEXTURE_UPLOAD
|
|
std::size_t readytexturecount{ 0 };
|
|
std::size_t readytexturesize{ 0 };
|
|
#endif
|
|
|
|
for( auto const& texture : m_textures ) {
|
|
|
|
totaltexturesize += texture.first->size;
|
|
#ifdef EU07_DEFERRED_TEXTURE_UPLOAD
|
|
|
|
if( texture.first->is_ready ) {
|
|
|
|
++readytexturecount;
|
|
readytexturesize += texture.first->size;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
return
|
|
"textures: "
|
|
#ifdef EU07_DEFERRED_TEXTURE_UPLOAD
|
|
+ std::to_string( readytexturecount )
|
|
+ " ("
|
|
+ to_string( readytexturesize / 1024.0f, 2 ) + " mb)"
|
|
+ " in vram, "
|
|
#endif
|
|
+ std::to_string( totaltexturecount )
|
|
+ " ("
|
|
+ to_string( totaltexturesize / 1024.0f, 2 ) + " mb)"
|
|
+ " total";
|
|
}
|
|
|
|
// checks whether specified texture is in the texture bank. returns texture id, or npos.
|
|
texture_handle
|
|
texture_manager::find_in_databank( std::string const &Texturename ) const {
|
|
|
|
std::vector<std::string> const filenames {
|
|
Global.asCurrentTexturePath + Texturename,
|
|
Texturename,
|
|
paths::textures + Texturename };
|
|
|
|
for( auto const &filename : filenames ) {
|
|
auto const lookup { m_texturemappings.find( filename ) };
|
|
if( lookup != m_texturemappings.end() ) {
|
|
return lookup->second;
|
|
}
|
|
}
|
|
// all lookups failed
|
|
return npos;
|
|
}
|
|
|
|
// checks whether specified file exists.
|
|
std::pair<std::string, std::string>
|
|
texture_manager::find_on_disk( std::string const &Texturename ) {
|
|
|
|
std::vector<std::string> const filenames {
|
|
Global.asCurrentTexturePath + Texturename,
|
|
Texturename,
|
|
paths::textures + Texturename };
|
|
|
|
auto lookup =
|
|
FileExists(
|
|
filenames,
|
|
{ Global.szDefaultExt } );
|
|
|
|
if( false == lookup.first.empty() ) {
|
|
return lookup;
|
|
}
|
|
|
|
// if the first attempt fails, try entire extension list
|
|
// NOTE: slightly wasteful as it means preferred extension is tested twice, but, eh
|
|
return FileExists(filenames, {".dds", ".tga", ".ktx", ".png", ".bmp", ".jpg", ".tex"});
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|