424 lines
11 KiB
C++
424 lines
11 KiB
C++
#include "render/gl_texture.h"
|
|
#include "main/references.h"
|
|
#include "main/logging.h"
|
|
#include <assert.h>
|
|
#include <algorithm>
|
|
|
|
namespace render {
|
|
extern bool isIntelCard;
|
|
|
|
void GLTexture::bind() {
|
|
glBindTexture(GL_TEXTURE_2D, glName);
|
|
}
|
|
|
|
unsigned GLTexture::getID() const {
|
|
return glName;
|
|
}
|
|
|
|
GLTexture::GLTexture()
|
|
: glName(0), pixelDepth(1)
|
|
{
|
|
type = TT_2D;
|
|
loaded = false;
|
|
}
|
|
|
|
GLTexture::GLTexture(Image& image, bool mipmap, bool cachePixels)
|
|
: glName(0), pixelDepth(1)
|
|
{
|
|
type = TT_2D;
|
|
loaded = false;
|
|
load(image, mipmap, cachePixels);
|
|
}
|
|
|
|
void createTexture(Image& image, bool mipmap) {
|
|
GLenum format;
|
|
|
|
unsigned levels = 1;
|
|
if(mipmap) {
|
|
unsigned smallDim = std::min(image.width, image.height);
|
|
while(smallDim > 2) {
|
|
smallDim /= 2;
|
|
levels += 1;
|
|
}
|
|
}
|
|
|
|
if(GLEW_ARB_texture_storage) {
|
|
switch(image.format) {
|
|
case FMT_Grey:
|
|
format = GL_LUMINANCE8;
|
|
//{
|
|
// GLint swizzleMask[] = {GL_RED, GL_RED, GL_RED, GL_ONE};
|
|
// glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask);
|
|
//}
|
|
break;
|
|
case FMT_Alpha:
|
|
format = GL_ALPHA8;
|
|
//{
|
|
// GLint swizzleMask[] = {GL_ONE, GL_ONE, GL_ONE, GL_RED};
|
|
// glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask);
|
|
//}
|
|
break;
|
|
case FMT_RGB:
|
|
format = GL_RGB8; break;
|
|
case FMT_RGBA:
|
|
format = GL_RGBA8; break;
|
|
NO_DEFAULT
|
|
}
|
|
glTexStorage2D(GL_TEXTURE_2D, levels, format, image.width, image.height);
|
|
|
|
if(!isIntelCard)
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, levels-1);
|
|
}
|
|
else {
|
|
//Sized lum/alpha are from texture storage arb
|
|
switch(image.format) {
|
|
case FMT_Grey:
|
|
format = GL_LUMINANCE; break;
|
|
case FMT_Alpha:
|
|
format = GL_ALPHA; break;
|
|
case FMT_RGB:
|
|
format = GL_RGB; break;
|
|
case FMT_RGBA:
|
|
format = GL_RGBA; break;
|
|
NO_DEFAULT
|
|
}
|
|
|
|
unsigned w = image.width, h = image.height;
|
|
for(unsigned i = 0; i < levels; ++i) {
|
|
glTexImage2D(GL_TEXTURE_2D, i, format, w, h, 0, format, GL_UNSIGNED_BYTE, nullptr);
|
|
w /= 2;
|
|
h /= 2;
|
|
}
|
|
|
|
if(!isIntelCard)
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, levels-1);
|
|
}
|
|
devices.render->reportErrors("creating texture");
|
|
}
|
|
|
|
void loadTexture(Image& image, const recti& pixels, unsigned lod = 0) {
|
|
devices.render->reportErrors();
|
|
|
|
GLenum format;
|
|
switch(image.format) {
|
|
case FMT_Grey:
|
|
format = GL_LUMINANCE; break;
|
|
case FMT_Alpha:
|
|
format = GL_ALPHA; break;
|
|
case FMT_RGB:
|
|
format = GL_RGB; break;
|
|
case FMT_RGBA:
|
|
format = GL_RGBA; break;
|
|
NO_DEFAULT
|
|
}
|
|
|
|
assert((unsigned)(pixels.topLeft.x + pixels.getWidth()) <= image.width);
|
|
assert((unsigned)(pixels.topLeft.y + pixels.getHeight()) <= image.height);
|
|
|
|
if(pixels.getSize() == vec2i(image.width, image.height)) {
|
|
void* ptrPixels;
|
|
switch(image.format) {
|
|
case FMT_Grey:
|
|
ptrPixels = (void*)image.grey; break;
|
|
case FMT_Alpha:
|
|
ptrPixels = (void*)image.grey; break;
|
|
case FMT_RGB:
|
|
ptrPixels = (void*)image.rgb; break;
|
|
case FMT_RGBA:
|
|
ptrPixels = (void*)image.rgba; break;
|
|
NO_DEFAULT
|
|
}
|
|
|
|
glTexSubImage2D(GL_TEXTURE_2D, lod, pixels.topLeft.x, pixels.topLeft.y, pixels.getWidth(), pixels.getHeight(), format, GL_UNSIGNED_BYTE, ptrPixels);
|
|
}
|
|
else if((unsigned)pixels.getWidth() == image.width) {
|
|
assert(pixels.topLeft.x == 0);
|
|
|
|
void* ptrPixels;
|
|
switch(image.format) {
|
|
case FMT_Grey:
|
|
ptrPixels = (void*)&image.get_grey(0, pixels.topLeft.y); break;
|
|
case FMT_Alpha:
|
|
ptrPixels = (void*)&image.get_alpha(0, pixels.topLeft.y); break;
|
|
case FMT_RGB:
|
|
ptrPixels = (void*)&image.get_rgb(0, pixels.topLeft.y); break;
|
|
case FMT_RGBA:
|
|
ptrPixels = (void*)&image.get_rgba(0, pixels.topLeft.y); break;
|
|
NO_DEFAULT
|
|
}
|
|
|
|
glTexSubImage2D(GL_TEXTURE_2D, lod, pixels.topLeft.x, pixels.topLeft.y, pixels.getWidth(), pixels.getHeight(), format, GL_UNSIGNED_BYTE, ptrPixels);
|
|
}
|
|
else {
|
|
//Must load line by line
|
|
for(unsigned y = pixels.topLeft.y, endY = pixels.botRight.y; y < endY; ++y) {
|
|
void* ptrPixels;
|
|
switch(image.format) {
|
|
case FMT_Grey:
|
|
ptrPixels = (void*)&image.get_grey(pixels.topLeft.x, y); break;
|
|
case FMT_Alpha:
|
|
ptrPixels = (void*)&image.get_alpha(pixels.topLeft.x, y); break;
|
|
case FMT_RGB:
|
|
ptrPixels = (void*)&image.get_rgb(pixels.topLeft.x, y); break;
|
|
case FMT_RGBA:
|
|
ptrPixels = (void*)&image.get_rgba(pixels.topLeft.x, y); break;
|
|
NO_DEFAULT
|
|
}
|
|
|
|
glTexSubImage2D(GL_TEXTURE_2D, lod, pixels.topLeft.x, y, pixels.getWidth(), 1, format, GL_UNSIGNED_BYTE, ptrPixels);
|
|
}
|
|
}
|
|
devices.render->reportErrors("filling sub image");
|
|
}
|
|
|
|
void GLTexture::loadStart(Image& image, bool mipmap, bool cachePixels, unsigned lod) {
|
|
devices.render->reportErrors();
|
|
loaded = true;
|
|
|
|
if(lod == 0) {
|
|
//Destroy old texture
|
|
if(glName != 0) {
|
|
glDeleteTextures(1, &glName);
|
|
glName = 0;
|
|
}
|
|
|
|
//Generate a new texture
|
|
glGenTextures(1,&glName);
|
|
|
|
//Store texture size
|
|
size = vec2i(image.width, image.height);
|
|
|
|
pixelDepth = ColorDepths[image.format];
|
|
|
|
if(cachePixels)
|
|
activePixels.resize(size.x * size.y);
|
|
}
|
|
|
|
glBindTexture(GL_TEXTURE_2D, glName);
|
|
if(lod == 0)
|
|
createTexture(image, mipmap);
|
|
}
|
|
|
|
void GLTexture::loadPartial(Image& image, const recti& pixels, bool cachePixels, unsigned lod) {
|
|
glBindTexture(GL_TEXTURE_2D, glName);
|
|
loadTexture(image, pixels, lod);
|
|
|
|
if(cachePixels && lod == 0) {
|
|
for(int y = pixels.topLeft.y; y < pixels.getHeight(); ++y) {
|
|
for(int x = pixels.topLeft.x; x < pixels.getWidth(); ++x) {
|
|
unsigned char a = image.get(x, y).a;
|
|
activePixels[y * size.width + x] = a != 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void GLTexture::loadFinish(bool mipmap, unsigned lod) {
|
|
devices.render->reportErrors();
|
|
glBindTexture(GL_TEXTURE_2D, glName);
|
|
|
|
hasMipMaps = mipmap;
|
|
if(mipmap) {
|
|
//Set linear mipmap filter
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
|
|
|
if(lod == 0) {
|
|
if(size.width * size.height > (1024 * 1024))
|
|
glHint(GL_GENERATE_MIPMAP_HINT, GL_FASTEST);
|
|
else
|
|
glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST);
|
|
|
|
glGenerateMipmap(GL_TEXTURE_2D);
|
|
}
|
|
}
|
|
else if(lod == 0) {
|
|
//Set linear filter
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
}
|
|
else {
|
|
//We're defining mipmap data now, switch back to mipmaping
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, lod);
|
|
}
|
|
|
|
devices.render->reportErrors("finalizing texture");
|
|
}
|
|
|
|
unsigned GLTexture::getTextureBytes() const {
|
|
unsigned bytes = size.width * size.height * pixelDepth;
|
|
if(hasMipMaps)
|
|
bytes += bytes / 3;
|
|
return bytes;
|
|
}
|
|
|
|
void GLTexture::load(Image& image, bool mipmap, bool cachePixels) {
|
|
loadStart(image, mipmap, cachePixels);
|
|
loadPartial(image, recti(0,0, image.width, image.height), cachePixels);
|
|
loadFinish(mipmap);
|
|
}
|
|
|
|
void GLTexture::save(Image& image) const {
|
|
image.resize(size.width, size.height, FMT_RGBA);
|
|
glBindTexture(GL_TEXTURE_2D, glName);
|
|
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, image.rgba);
|
|
}
|
|
|
|
bool GLTexture::isPixelActive(vec2i px) const {
|
|
unsigned index = px.y * size.width + px.x;
|
|
if(activePixels.empty())
|
|
return true;
|
|
if(index >= activePixels.size())
|
|
return false;
|
|
return activePixels[index];
|
|
}
|
|
|
|
GLTexture::~GLTexture() {
|
|
glDeleteTextures(1, &glName);
|
|
}
|
|
|
|
bool GLCubeMap::isPixelActive(vec2i px) const {
|
|
return true;
|
|
}
|
|
|
|
void GLCubeMap::load(Image& image, bool mipmap, bool cachePixels) {
|
|
devices.render->reportErrors();
|
|
loaded = false;
|
|
|
|
vec2u tile = vec2u(image.width / 4, image.height / 3);
|
|
if(tile.width != tile.height || tile.width == 0) {
|
|
error("ERROR loading cubemap: Cubemap must have a 4:3 aspect ratio.");
|
|
return;
|
|
}
|
|
|
|
//Destroy old texture
|
|
if(glName != 0) {
|
|
glDeleteTextures(1, &glName);
|
|
glName = 0;
|
|
}
|
|
|
|
//Generate a new texture
|
|
glGenTextures(1,&glName);
|
|
|
|
//Store texture size
|
|
size = vec2i(image.width, image.height);
|
|
loaded = true;
|
|
|
|
pixelDepth = ColorDepths[image.format];
|
|
|
|
glBindTexture(GL_TEXTURE_CUBE_MAP, glName);
|
|
|
|
unsigned char* data = image.grey;
|
|
|
|
GLenum format;
|
|
switch(image.format) {
|
|
case FMT_Grey:
|
|
format = GL_LUMINANCE; break;
|
|
case FMT_Alpha:
|
|
format = GL_ALPHA; break;
|
|
case FMT_RGB:
|
|
format = GL_RGB; break;
|
|
case FMT_RGBA:
|
|
format = GL_RGBA; break;
|
|
NO_DEFAULT
|
|
}
|
|
|
|
auto getTile = [&](unsigned x, unsigned y) -> Image* {
|
|
vec2u top = vec2u(tile.width * x, tile.height * y);
|
|
return image.crop(rect<unsigned>(top, top + tile));
|
|
};
|
|
|
|
auto* img = getTile(1,1);
|
|
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, format, tile.width, tile.height, 0, format, GL_UNSIGNED_BYTE, img->grey);
|
|
delete img;
|
|
|
|
img = getTile(2,1);
|
|
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, format, tile.width, tile.height, 0, format, GL_UNSIGNED_BYTE, img->grey);
|
|
delete img;
|
|
|
|
img = getTile(1,2);
|
|
glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, format, tile.width, tile.height, 0, format, GL_UNSIGNED_BYTE, img->grey);
|
|
delete img;
|
|
|
|
img = getTile(3,1);
|
|
glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, format, tile.width, tile.height, 0, format, GL_UNSIGNED_BYTE, img->grey);
|
|
delete img;
|
|
|
|
img = getTile(0,1);
|
|
glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, format, tile.width, tile.height, 0, format, GL_UNSIGNED_BYTE, img->grey);
|
|
delete img;
|
|
|
|
img = getTile(1,0);
|
|
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, format, tile.width, tile.height, 0, format, GL_UNSIGNED_BYTE, img->grey);
|
|
delete img;
|
|
|
|
devices.render->reportErrors("loading cubemap images");
|
|
|
|
hasMipMaps = mipmap;
|
|
if(mipmap) {
|
|
//Set linear mipmap filter
|
|
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
|
|
|
if(size.width * size.height > (1024 * 1024))
|
|
glHint(GL_GENERATE_MIPMAP_HINT, GL_FASTEST);
|
|
else
|
|
glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST);
|
|
|
|
glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
|
|
}
|
|
else {
|
|
//Set linear filter
|
|
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
}
|
|
|
|
devices.render->reportErrors("finalizing texture");
|
|
}
|
|
|
|
unsigned GLCubeMap::getTextureBytes() const {
|
|
unsigned bytes = size.width * size.height * pixelDepth * 6;
|
|
if(hasMipMaps)
|
|
bytes += bytes / 3;
|
|
return bytes;
|
|
}
|
|
|
|
void GLCubeMap::save(Image& image) const {}
|
|
|
|
void GLCubeMap::bind() {
|
|
glBindTexture(GL_TEXTURE_CUBE_MAP, glName);
|
|
}
|
|
|
|
unsigned GLCubeMap::getID() const {
|
|
return glName;
|
|
}
|
|
|
|
GLCubeMap::GLCubeMap(Image& image, bool mipmap) : glName(0), pixelDepth(1) {
|
|
loaded = false;
|
|
type = TT_Cubemap;
|
|
load(image, mipmap);
|
|
}
|
|
|
|
GLCubeMap::GLCubeMap() : glName(0), pixelDepth(0) {
|
|
loaded = false;
|
|
type = TT_Cubemap;
|
|
}
|
|
|
|
GLCubeMap::~GLCubeMap() {
|
|
glDeleteTextures(1, &glName);
|
|
}
|
|
|
|
void GLCubeMap::loadStart(Image& image, bool mipmap, bool cachePixels, unsigned lod) {
|
|
load(image, mipmap);
|
|
}
|
|
|
|
void GLCubeMap::loadPartial(Image& image, const recti& pixels, bool cachePixels, unsigned lod) {
|
|
}
|
|
|
|
void GLCubeMap::loadFinish(bool mipmap, unsigned lod) {
|
|
}
|
|
|
|
};
|