celestia/src/celengine/texture.cpp

999 lines
27 KiB
C++

// texture.cpp
//
// Copyright (C) 2001-2003, Chris Laurel
//
// This program 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 2
// of the License, or (at your option) any later version.
#include <algorithm>
#include <cassert>
#include <cstdlib>
#include <cmath>
#include <fstream>
#include <iostream>
#include <Eigen/Core>
#include "glsupport.h"
#include <celutil/filetype.h>
#include <celutil/gettext.h>
#include <celutil/logger.h>
#include "framebuffer.h"
#include "texture.h"
#include "virtualtex.h"
using namespace celestia;
using namespace Eigen;
using namespace std;
using celestia::util::GetLogger;
struct TextureCaps
{
GLint preferredAnisotropy;
};
static TextureCaps texCaps;
#undef DUMP_TEXTURE_MIPMAP_INFO
#ifdef DUMP_TEXTURE_MIPMAP_INFO
static void DumpTextureMipmapInfo(GLenum target)
{
for (int i = 0; i < 16; i++)
{
GLint w = 0, h = 0;
glGetTexLevelParameteriv(target, i, GL_TEXTURE_HEIGHT, &h);
if (glGetError() != GL_NO_ERROR) break;
glGetTexLevelParameteriv(target, i, GL_TEXTURE_WIDTH, &w);
if (glGetError() != GL_NO_ERROR) break;
if (w == 0 || h == 0) break;
cout << w << 'x' << h << '\n';
}
}
#else
#define DumpTextureMipmapInfo(target) (void)target
#endif
static bool testMaxLevel()
{
#ifndef GL_ES
unsigned char texels[64];
GLuint textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_2D, textureID);
// Test whether GL_TEXTURE_MAX_LEVEL is supported . . .
glTexImage2D(GL_TEXTURE_2D,
0,
GL_LUMINANCE,
8, 8,
0,
GL_LUMINANCE,
GL_UNSIGNED_BYTE,
texels);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 2);
float maxLev = -1.0f;
glGetTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, &maxLev);
glDeleteTextures(1, &textureID);
return maxLev == 2;
#else
return false;
#endif
}
static const TextureCaps& GetTextureCaps()
{
static bool texCapsInitialized = false;
if (!texCapsInitialized)
{
texCapsInitialized = true;
texCaps.preferredAnisotropy = 1;
#ifndef GL_ES
if (gl::EXT_texture_filter_anisotropic)
{
// Cap the preferred level texture anisotropy to 8; eventually, we should allow
// the user to control this.
texCaps.preferredAnisotropy = min(8, gl::maxTextureAnisotropy);
}
#endif
}
return texCaps;
}
static int getInternalFormat(PixelFormat format)
{
#ifdef GL_ES
switch (format)
{
case PixelFormat::RGBA:
case PixelFormat::RGB:
case PixelFormat::LUM_ALPHA:
case PixelFormat::ALPHA:
case PixelFormat::LUMINANCE:
case PixelFormat::DXT1:
case PixelFormat::DXT3:
case PixelFormat::DXT5:
return (int) format;
default:
return 0;
}
#else
switch (format)
{
case PixelFormat::RGBA:
case PixelFormat::BGRA:
case PixelFormat::RGB:
case PixelFormat::BGR:
case PixelFormat::LUM_ALPHA:
case PixelFormat::ALPHA:
case PixelFormat::LUMINANCE:
case PixelFormat::DXT1:
case PixelFormat::DXT3:
case PixelFormat::DXT5:
return (int) format;
default:
return 0;
}
#endif
}
#if 0
// Required in order to support on-the-fly compression; currently, this
// feature is disabled.
static int getCompressedInternalFormat(int format)
{
switch (format)
{
case GL_RGB:
case GL_BGR:
return GL_COMPRESSED_RGB;
case GL_RGBA:
case GL_BGRA:
return GL_COMPRESSED_RGBA;
case GL_ALPHA:
return GL_COMPRESSED_ALPHA;
case GL_LUMINANCE:
return GL_COMPRESSED_LUMINANCE;
case GL_LUMINANCE_ALPHA:
return GL_COMPRESSED_LUMINANCE_ALPHA;
case GL_INTENSITY:
return GL_COMPRESSED_INTENSITY;
case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
return format;
default:
return 0;
}
}
#endif
static int getCompressedBlockSize(PixelFormat format)
{
return format == PixelFormat::DXT1 ? 8 : 16;
}
static GLenum GetGLTexAddressMode(Texture::AddressMode addressMode)
{
switch (addressMode)
{
case Texture::Wrap:
return GL_REPEAT;
case Texture::EdgeClamp:
return GL_CLAMP_TO_EDGE;
case Texture::BorderClamp:
#ifndef GL_ES
return GL_CLAMP_TO_BORDER;
#else
return GL_CLAMP_TO_BORDER_OES;
#endif
}
return 0;
}
static void SetBorderColor(Color borderColor, GLenum target)
{
float bc[4] = { borderColor.red(), borderColor.green(),
borderColor.blue(), borderColor.alpha() };
#ifndef GL_ES
glTexParameterfv(target, GL_TEXTURE_BORDER_COLOR, bc);
#else
glTexParameterfv(target, GL_TEXTURE_BORDER_COLOR_OES, bc);
#endif
}
// Load a prebuilt set of mipmaps; assumes that the image contains
// a complete set of mipmap levels.
static void LoadMipmapSet(Image& img, GLenum target)
{
int internalFormat = getInternalFormat(img.getFormat());
for (int mip = 0; mip < img.getMipLevelCount(); mip++)
{
unsigned int mipWidth = max((unsigned int) img.getWidth() >> mip, 1u);
unsigned int mipHeight = max((unsigned int) img.getHeight() >> mip, 1u);
if (img.isCompressed())
{
glCompressedTexImage2D(target,
mip,
internalFormat,
mipWidth, mipHeight,
0,
img.getMipLevelSize(mip),
img.getMipLevel(mip));
}
else
{
glTexImage2D(target,
mip,
internalFormat,
mipWidth, mipHeight,
0,
(GLenum) img.getFormat(),
GL_UNSIGNED_BYTE,
img.getMipLevel(mip));
}
}
}
// Load a texture without any mipmaps
static void LoadMiplessTexture(Image& img, GLenum target)
{
int internalFormat = getInternalFormat(img.getFormat());
if (img.isCompressed())
{
glCompressedTexImage2D(target,
0,
internalFormat,
img.getWidth(), img.getHeight(),
0,
img.getMipLevelSize(0),
img.getMipLevel(0));
}
else
{
glTexImage2D(target,
0,
internalFormat,
img.getWidth(), img.getHeight(),
0,
(GLenum) img.getFormat(),
GL_UNSIGNED_BYTE,
img.getMipLevel(0));
}
}
static int ilog2(unsigned int x)
{
int n = -1;
while (x != 0)
{
x >>= 1;
n++;
}
return n;
}
static int CalcMipLevelCount(int w, int h)
{
return max(ilog2(w), ilog2(h)) + 1;
}
Texture::Texture(int w, int h, int d) :
width(w),
height(h),
depth(d)
{
}
int Texture::getLODCount() const
{
return 1;
}
int Texture::getUTileCount(int /*unused*/) const
{
return 1;
}
int Texture::getVTileCount(int /*unused*/) const
{
return 1;
}
int Texture::getWTileCount(int /*unused*/) const
{
return 1;
}
void Texture::setBorderColor(Color /*unused*/)
{
}
int Texture::getWidth() const
{
return width;
}
int Texture::getHeight() const
{
return height;
}
int Texture::getDepth() const
{
return depth;
}
unsigned int Texture::getFormatOptions() const
{
return formatOptions;
}
void Texture::setFormatOptions(unsigned int opts)
{
formatOptions = opts;
}
ImageTexture::ImageTexture(Image& img,
AddressMode addressMode,
MipMapMode mipMapMode) :
Texture(img.getWidth(), img.getHeight()),
glName(0)
{
glGenTextures(1, (GLuint*) &glName);
glBindTexture(GL_TEXTURE_2D, glName);
bool mipmap = mipMapMode != NoMipMaps;
bool precomputedMipMaps = false;
// Use precomputed mipmaps only if a complete set is supplied
int mipLevelCount = img.getMipLevelCount();
if (mipmap && mipLevelCount == CalcMipLevelCount(img.getWidth(), img.getHeight()))
{
precomputedMipMaps = true;
}
// We can't automatically generate mipmaps for compressed textures.
// If a precomputed mipmap set isn't provided, turn off mipmapping entirely.
if (!precomputedMipMaps && img.isCompressed())
{
mipmap = false;
}
GLenum texAddress = GetGLTexAddressMode(addressMode);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, texAddress);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, texAddress);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
mipmap ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR);
#ifndef GL_ES
if (gl::EXT_texture_filter_anisotropic && texCaps.preferredAnisotropy > 1)
{
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, texCaps.preferredAnisotropy);
}
#endif
bool genMipmaps = mipmap && !precomputedMipMaps;
#if !defined(GL_ES)
if (genMipmaps && !FramebufferObject::isSupported())
glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
#endif
if (mipmap)
{
if (precomputedMipMaps)
LoadMipmapSet(img, GL_TEXTURE_2D);
else if (mipMapMode == DefaultMipMaps)
LoadMiplessTexture(img, GL_TEXTURE_2D);
}
else
{
LoadMiplessTexture(img, GL_TEXTURE_2D);
}
if (genMipmaps && FramebufferObject::isSupported())
glGenerateMipmap(GL_TEXTURE_2D);
DumpTextureMipmapInfo(GL_TEXTURE_2D);
alpha = img.hasAlpha();
compressed = img.isCompressed();
}
ImageTexture::~ImageTexture()
{
if (glName != 0)
glDeleteTextures(1, (const GLuint*) &glName);
}
void ImageTexture::bind()
{
glBindTexture(GL_TEXTURE_2D, glName);
}
const TextureTile ImageTexture::getTile(int lod, int u, int v)
{
if (lod != 0 || u != 0 || v != 0)
return TextureTile(0);
return TextureTile(glName);
}
unsigned int ImageTexture::getName() const
{
return glName;
}
void ImageTexture::setBorderColor(Color borderColor)
{
bind();
SetBorderColor(borderColor, GL_TEXTURE_2D);
}
TiledTexture::TiledTexture(Image& img,
int _uSplit, int _vSplit,
MipMapMode mipMapMode) :
Texture(img.getWidth(), img.getHeight()),
uSplit(_uSplit),
vSplit(_vSplit),
glNames(nullptr)
{
glNames = new unsigned int[uSplit * vSplit];
{
for (int i = 0; i < uSplit * vSplit; i++)
glNames[i] = 0;
}
alpha = img.hasAlpha();
compressed = img.isCompressed();
bool mipmap = mipMapMode != NoMipMaps;
bool precomputedMipMaps = false;
// Require a complete set of mipmaps
int mipLevelCount = img.getMipLevelCount();
int completeMipCount = CalcMipLevelCount(img.getWidth(), img.getHeight());
// Allow a bit of slack here--it turns out that some tools don't want to
// calculate the 1x1 mip level. Rather than turn off mipmaps, we'll just
// point the 1x1 mip to the 2x1.
if (mipmap && mipLevelCount >= completeMipCount - 1)
precomputedMipMaps = true;
// We can't automatically generate mipmaps for compressed textures.
// If a precomputed mipmap set isn't provided, turn of mipmapping entirely.
if (!precomputedMipMaps && img.isCompressed())
mipmap = false;
GLenum texAddress = GetGLTexAddressMode(EdgeClamp);
int components = img.getComponents();
// Create a temporary image which we'll use for the tile texels
int tileWidth = img.getWidth() / uSplit;
int tileHeight = img.getHeight() / vSplit;
int tileMipLevelCount = CalcMipLevelCount(tileWidth, tileHeight);
Image* tile = new Image(img.getFormat(),
tileWidth, tileHeight,
tileMipLevelCount);
for (int v = 0; v < vSplit; v++)
{
for (int u = 0; u < uSplit; u++)
{
// Create the texture and set up sampling and addressing
glGenTextures(1, (GLuint*)&glNames[v * uSplit + u]);
glBindTexture(GL_TEXTURE_2D, glNames[v * uSplit + u]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, texAddress);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, texAddress);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
mipmap ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR);
#ifndef GL_ES
if (gl::EXT_texture_filter_anisotropic && texCaps.preferredAnisotropy > 1)
{
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, texCaps.preferredAnisotropy);
}
#endif
// Copy texels from the subtexture area to the pixel buffer. This
// is straightforward for normal textures, but an immense headache
// for compressed textures with prebuilt mipmaps.
if (precomputedMipMaps)
{
if (img.isCompressed())
{
for (int mip = 0; mip < tileMipLevelCount; mip++)
{
int blockSize = getCompressedBlockSize(img.getFormat());
unsigned char* imgMip =
img.getMipLevel(min(mip, mipLevelCount));
unsigned int mipWidth = max((unsigned int) img.getWidth() >> mip, 1u);
unsigned char* tileMip = tile->getMipLevel(mip);
unsigned int tileMipWidth = max((unsigned int) tile->getWidth() >> mip, 1u);
unsigned int tileMipHeight = max((unsigned int) tile->getHeight() >> mip, 1u);
int uBlocks = max(tileMipWidth / 4, 1u);
int vBlocks = max(tileMipHeight / 4, 1u);
int destBytesPerRow = uBlocks * blockSize;
int srcBytesPerRow = max(mipWidth / 4, 1u) * blockSize;
int srcU = u * tileMipWidth / 4;
int srcV = v * tileMipHeight / 4;
int tileOffset = srcV * srcBytesPerRow +
srcU * blockSize;
for (int y = 0; y < vBlocks; y++)
{
memcpy(tileMip + y * destBytesPerRow,
imgMip + tileOffset + y * srcBytesPerRow,
destBytesPerRow);
}
}
}
else
{
// TODO: Handle uncompressed textures with prebuilt mipmaps
}
LoadMipmapSet(*tile, GL_TEXTURE_2D);
}
else
{
if (img.isCompressed())
{
int blockSize = getCompressedBlockSize(img.getFormat());
int uBlocks = max(tileWidth / 4, 1);
int vBlocks = max(tileHeight / 4, 1);
int destBytesPerRow = uBlocks * blockSize;
int srcBytesPerRow = max(img.getWidth() / 4, 1) * blockSize;
int srcU = u * tileWidth / 4;
int srcV = v * tileHeight / 4;
int tileOffset = srcV * srcBytesPerRow +
srcU * blockSize;
for (int y = 0; y < vBlocks; y++)
{
memcpy(tile->getPixels() + y * destBytesPerRow,
img.getPixels() + tileOffset + y * srcBytesPerRow,
destBytesPerRow);
}
}
else
{
unsigned char* tilePixels = img.getPixels() +
(v * tileHeight * img.getWidth() + u * tileWidth) * components;
for (int y = 0; y < tileHeight; y++)
{
memcpy(tile->getPixels() + y * tileWidth * components,
tilePixels + y * img.getWidth() * components,
tileWidth * components);
}
}
if (mipmap)
{
if (FramebufferObject::isSupported())
{
LoadMiplessTexture(*tile, GL_TEXTURE_2D);
glGenerateMipmap(GL_TEXTURE_2D);
}
#ifndef GL_ES
else
{
glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
LoadMiplessTexture(*tile, GL_TEXTURE_2D);
}
#endif
}
else
{
LoadMiplessTexture(*tile, GL_TEXTURE_2D);
}
DumpTextureMipmapInfo(GL_TEXTURE_2D);
}
}
}
delete tile;
}
TiledTexture::~TiledTexture()
{
if (glNames != nullptr)
{
for (int i = 0; i < uSplit * vSplit; i++)
{
if (glNames[i] != 0)
glDeleteTextures(1, (const GLuint*) &glNames[i]);
}
delete[] glNames;
}
}
void TiledTexture::bind()
{
}
void TiledTexture::setBorderColor(Color borderColor)
{
for (int i = 0; i < vSplit; i++)
{
for (int j = 0; j < uSplit; j++)
{
glBindTexture(GL_TEXTURE_2D, glNames[i * uSplit + j]);
SetBorderColor(borderColor, GL_TEXTURE_2D);
}
}
}
int TiledTexture::getUTileCount(int /*lod*/) const
{
return uSplit;
}
int TiledTexture::getVTileCount(int /*lod*/) const
{
return vSplit;
}
const TextureTile TiledTexture::getTile(int lod, int u, int v)
{
if (lod != 0 || u >= uSplit || u < 0 || v >= vSplit || v < 0)
return TextureTile(0);
return TextureTile(glNames[v * uSplit + u]);
}
CubeMap::CubeMap(Image* faces[]) :
Texture(faces[0]->getWidth(), faces[0]->getHeight()),
glName(0)
{
// Verify that all the faces are square and have the same size
int width = faces[0]->getWidth();
PixelFormat format = faces[0]->getFormat();
int i = 0;
for (i = 0; i < 6; i++)
{
if (faces[i]->getWidth() != width ||
faces[i]->getHeight() != width ||
faces[i]->getFormat() != format)
return;
}
// For now, always enable mipmaps; in the future, it should be possible to
// override this.
bool mipmap = true;
bool precomputedMipMaps = false;
// Require a complete set of mipmaps
int mipLevelCount = faces[0]->getMipLevelCount();
if (mipmap && mipLevelCount == CalcMipLevelCount(width, width))
precomputedMipMaps = true;
// We can't automatically generate mipmaps for compressed textures.
// If a precomputed mipmap set isn't provided, turn of mipmapping entirely.
if (!precomputedMipMaps && faces[0]->isCompressed())
mipmap = false;
glGenTextures(1, (GLuint*) &glName);
glBindTexture(GL_TEXTURE_CUBE_MAP, glName);
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);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER,
mipmap ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR);
bool genMipmaps = mipmap && !precomputedMipMaps;
#if !defined(GL_ES)
if (genMipmaps && !FramebufferObject::isSupported())
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_GENERATE_MIPMAP, GL_TRUE);
#endif
for (i = 0; i < 6; i++)
{
auto targetFace = (GLenum) ((int) GL_TEXTURE_CUBE_MAP_POSITIVE_X + i);
Image* face = faces[i];
if (mipmap)
{
if (precomputedMipMaps)
LoadMipmapSet(*face, targetFace);
else
LoadMiplessTexture(*face, targetFace);
}
else
{
LoadMiplessTexture(*face, targetFace);
}
}
if (genMipmaps && FramebufferObject::isSupported())
glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
DumpTextureMipmapInfo(GL_TEXTURE_CUBE_MAP_POSITIVE_X);
}
CubeMap::~CubeMap()
{
if (glName != 0)
glDeleteTextures(1, (const GLuint*) &glName);
}
void CubeMap::bind()
{
glBindTexture(GL_TEXTURE_CUBE_MAP, glName);
}
const TextureTile CubeMap::getTile(int lod, int u, int v)
{
if (lod != 0 || u != 0 || v != 0)
return TextureTile(0);
return TextureTile(glName);
}
void CubeMap::setBorderColor(Color borderColor)
{
bind();
SetBorderColor(borderColor, GL_TEXTURE_CUBE_MAP);
}
Texture* CreateProceduralTexture(int width, int height,
PixelFormat format,
ProceduralTexEval func,
Texture::AddressMode addressMode,
Texture::MipMapMode mipMode)
{
Image* img = new Image(format, width, height);
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
float u = ((float) x + 0.5f) / (float) width * 2 - 1;
float v = ((float) y + 0.5f) / (float) height * 2 - 1;
func(u, v, 0, img->getPixelRow(y) + x * img->getComponents());
}
}
Texture* tex = new ImageTexture(*img, addressMode, mipMode);
delete img;
return tex;
}
Texture* CreateProceduralTexture(int width, int height,
PixelFormat format,
TexelFunctionObject& func,
Texture::AddressMode addressMode,
Texture::MipMapMode mipMode)
{
Image* img = new Image(format, width, height);
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
float u = ((float) x + 0.5f) / (float) width * 2 - 1;
float v = ((float) y + 0.5f) / (float) height * 2 - 1;
func(u, v, 0, img->getPixelRow(y) + x * img->getComponents());
}
}
Texture* tex = new ImageTexture(*img, addressMode, mipMode);
delete img;
return tex;
}
// Helper function for CreateProceduralCubeMap; return the normalized
// vector pointing to (s, t) on the specified face.
static Vector3f cubeVector(int face, float s, float t)
{
Vector3f v;
switch (face)
{
case 0:
v = Vector3f(1.0f, -t, -s);
break;
case 1:
v = Vector3f(-1.0f, -t, s);
break;
case 2:
v = Vector3f(s, 1.0f, t);
break;
case 3:
v = Vector3f(s, -1.0f, -t);
break;
case 4:
v = Vector3f(s, -t, 1.0f);
break;
case 5:
v = Vector3f(-s, -t, -1.0f);
break;
default:
// assert(false);
break;
}
v.normalize();
return v;
}
Texture* CreateProceduralCubeMap(int size,
PixelFormat format,
ProceduralTexEval func)
{
Image* faces[6];
for (int i = 0; i < 6; i++)
{
faces[i] = new Image(format, size, size);
Image* face = faces[i];
for (int y = 0; y < size; y++)
{
for (int x = 0; x < size; x++)
{
float s = ((float) x + 0.5f) / (float) size * 2 - 1;
float t = ((float) y + 0.5f) / (float) size * 2 - 1;
Vector3f v = cubeVector(i, s, t);
func(v.x(), v.y(), v.z(),
face->getPixelRow(y) + x * face->getComponents());
}
}
}
Texture* tex = new CubeMap(faces);
// Clean up the images
for (int i = 0; i < 6; i++)
{
delete faces[i];
}
return tex;
}
static Texture* CreateTextureFromImage(Image& img,
Texture::AddressMode addressMode,
Texture::MipMapMode mipMode)
{
Texture* tex = nullptr;
const int maxDim = gl::maxTextureSize;
if ((img.getWidth() > maxDim || img.getHeight() > maxDim))
{
// The texture is too large; we need to split it.
int uSplit = max(1, img.getWidth() / maxDim);
int vSplit = max(1, img.getHeight() / maxDim);
GetLogger()->info(_("Creating tiled texture. Width={}, max={}\n"),
img.getWidth(), maxDim);
tex = new TiledTexture(img, uSplit, vSplit, mipMode);
}
else
{
GetLogger()->info(_("Creating ordinary texture: {}x{}\n"),
img.getWidth(), img.getHeight());
tex = new ImageTexture(img, addressMode, mipMode);
}
return tex;
}
Texture* LoadTextureFromFile(const fs::path& filename,
Texture::AddressMode addressMode,
Texture::MipMapMode mipMode)
{
// Check for a Celestia texture--these need to be handled specially.
ContentType contentType = DetermineFileType(filename);
if (contentType == Content_CelestiaTexture)
return LoadVirtualTexture(filename);
// All other texture types are handled by first loading an image, then
// creating a texture from that image.
Image* img = LoadImageFromFile(filename);
if (img == nullptr)
return nullptr;
Texture* tex = CreateTextureFromImage(*img, addressMode, mipMode);
if (contentType == Content_DXT5NormalMap)
{
// If the texture came from a .dxt5nm file then mark it as a dxt5
// compressed normal map. There's no separate OpenGL format for dxt5
// normal maps, so the file extension is the only thing that
// distinguishes it from a plain old dxt5 texture.
if (img->getFormat() == PixelFormat::DXT5)
{
tex->setFormatOptions(Texture::DXT5NormalMap);
}
}
delete img;
return tex;
}
// Load a height map texture from a file and convert it to a normal map.
Texture* LoadHeightMapFromFile(const fs::path& filename,
float height,
Texture::AddressMode addressMode)
{
Image* img = LoadImageFromFile(filename);
if (img == nullptr)
return nullptr;
Image* normalMap = img->computeNormalMap(height,
addressMode == Texture::Wrap);
delete img;
if (normalMap == nullptr)
return nullptr;
Texture* tex = CreateTextureFromImage(*normalMap, addressMode,
Texture::DefaultMipMaps);
delete normalMap;
return tex;
}