Add support for 2d array textures; Improve mipmaps;

lovr.graphics.newTexture has been changed.
This commit is contained in:
bjorn 2018-02-20 17:15:00 -08:00
parent 5493a980ff
commit f75530b9e1
13 changed files with 145 additions and 128 deletions

View File

@ -86,6 +86,7 @@ extern map_int_t MeshUsages;
extern map_int_t PolygonWindings;
extern map_int_t ShapeTypes;
extern map_int_t TextureFormats;
extern map_int_t TextureTypes;
extern map_int_t TimeUnits;
extern map_int_t VerticalAligns;
extern map_int_t WrapModes;

View File

@ -29,6 +29,7 @@ map_int_t MeshDrawModes;
map_int_t MeshUsages;
map_int_t StencilActions;
map_int_t TextureFormats;
map_int_t TextureTypes;
map_int_t VerticalAligns;
map_int_t Windings;
map_int_t WrapModes;
@ -204,6 +205,11 @@ int l_lovrGraphicsInit(lua_State* L) {
map_set(&TextureFormats, "dxt3", FORMAT_DXT3);
map_set(&TextureFormats, "dxt5", FORMAT_DXT5);
map_init(&TextureTypes);
map_set(&TextureTypes, "2d", TEXTURE_2D);
map_set(&TextureTypes, "array", TEXTURE_ARRAY);
map_set(&TextureTypes, "cube", TEXTURE_CUBE);
map_init(&VerticalAligns);
map_set(&VerticalAligns, "top", ALIGN_TOP);
map_set(&VerticalAligns, "bottom", ALIGN_BOTTOM);
@ -918,7 +924,7 @@ int l_lovrGraphicsNewMaterial(lua_State* L) {
Blob* blob = luax_readblob(L, index++, "Texture");
TextureData* textureData = lovrTextureDataFromBlob(blob);
lovrRelease(&blob->ref);
Texture* texture = lovrTextureCreate(TEXTURE_2D, &textureData, 1, true);
Texture* texture = lovrTextureCreate(TEXTURE_2D, &textureData, 1, true, true);
lovrMaterialSetTexture(material, TEXTURE_DIFFUSE, texture);
lovrRelease(&texture->ref);
} else if (lua_isuserdata(L, index)) {
@ -1020,7 +1026,7 @@ int l_lovrGraphicsNewModel(lua_State* L) {
if (lua_type(L, 2) == LUA_TSTRING) {
Blob* blob = luax_readblob(L, 2, "Texture");
TextureData* textureData = lovrTextureDataFromBlob(blob);
Texture* texture = lovrTextureCreate(TEXTURE_2D, &textureData, 1, true);
Texture* texture = lovrTextureCreate(TEXTURE_2D, &textureData, 1, true, true);
Material* material = lovrMaterialCreate(false);
lovrMaterialSetTexture(material, TEXTURE_DIFFUSE, texture);
lovrModelSetMaterial(model, material);
@ -1061,34 +1067,57 @@ int l_lovrGraphicsNewShader(lua_State* L) {
}
int l_lovrGraphicsNewTexture(lua_State* L) {
int top = lua_gettop(L);
bool isTable = lua_istable(L, 1);
bool hasFlags = isTable ? lua_istable(L, 2) : lua_istable(L, top);
int count = isTable ? lua_objlen(L, 1) : (top - (hasFlags ? 1 : 0));
lovrAssert(count == 1 || count == 6, "Expected 1 image for a 2d texture or 6 images for a cubemap, got %d", count);
TextureData* slices[6];
if (isTable) {
for (int i = 0; i < count; i++) {
lua_rawgeti(L, 1, i + 1);
slices[i] = luax_checktexturedata(L, -1);
lua_pop(L, 1);
}
} else {
for (int i = 0; i < count; i++) {
slices[i] = luax_checktexturedata(L, i + 1);
}
if (!isTable) {
lua_newtable(L);
lua_pushvalue(L, 1);
lua_rawseti(L, -2, 1);
lua_replace(L, 1);
}
int sliceCount = lua_objlen(L, 1);
TextureType type = isTable ? (sliceCount > 0 ? TEXTURE_ARRAY : TEXTURE_CUBE) : TEXTURE_2D;
bool hasFlags = lua_istable(L, 2);
bool srgb = true;
bool mipmaps = true;
if (hasFlags) {
lua_getfield(L, top, "linear");
lua_getfield(L, 2, "linear");
srgb = !lua_toboolean(L, -1);
lua_pop(L, 1);
lua_getfield(L, 2, "mipmaps");
mipmaps = lua_toboolean(L, -1);
lua_pop(L, 1);
lua_getfield(L, 2, "type");
if (!lua_isnil(L, -1)) {
type = *(TextureType*) luax_checkenum(L, -1, &TextureTypes, "texture type");
}
lua_pop(L, 1);
}
if (type == TEXTURE_CUBE && sliceCount == 0) {
sliceCount = 6;
const char* faces[6] = { "left", "right", "top", "bottom", "front", "back" };
for (int i = 0; i < 6; i++) {
lua_pushstring(L, faces[i]);
lua_rawget(L, 1);
lua_rawseti(L, 1, i + 1);
}
}
Texture* texture = lovrTextureCreate(type, NULL, sliceCount, srgb, mipmaps);
for (int i = 0; i < sliceCount; i++) {
lua_rawgeti(L, 1, i + 1);
TextureData* textureData = luax_checktexturedata(L, -1);
lovrTextureReplacePixels(texture, textureData, i);
lua_pop(L, 1);
}
TextureType type = (count == 1) ? TEXTURE_2D : TEXTURE_CUBE;
Texture* texture = lovrTextureCreate(type, slices, count, srgb);
luax_pushtype(L, Texture, texture);
lovrRelease(&texture->ref);
return 1;

View File

@ -136,7 +136,6 @@ TextureData* lovrTextureDataGetBlank(int width, int height, uint8_t value, Textu
textureData->data = memset(malloc(size), value, size);
textureData->blob = NULL;
vec_init(&textureData->mipmaps);
textureData->generateMipmaps = false;
return textureData;
}
@ -151,7 +150,6 @@ TextureData* lovrTextureDataGetEmpty(int width, int height, TextureFormat format
textureData->data = NULL;
textureData->blob = NULL;
vec_init(&textureData->mipmaps);
textureData->generateMipmaps = false;
return textureData;
}
@ -171,7 +169,6 @@ TextureData* lovrTextureDataFromBlob(Blob* blob) {
textureData->format = FORMAT_RGBA;
textureData->data = stbi_load_from_memory(blob->data, blob->size, &textureData->width, &textureData->height, NULL, 4);
textureData->blob = NULL;
textureData->generateMipmaps = true;
if (!textureData->data) {
lovrThrow("Could not load texture data from '%s'", blob->name);

View File

@ -40,7 +40,6 @@ typedef struct {
void* data;
Blob* blob;
TextureFormat format;
bool generateMipmaps;
vec_mipmap_t mipmaps;
} TextureData;

View File

@ -21,7 +21,7 @@ bool lovrCanvasSupportsFormat(TextureFormat format) {
Canvas* lovrCanvasCreate(int width, int height, TextureFormat format, int msaa, bool depth, bool stencil) {
TextureData* textureData = lovrTextureDataGetEmpty(width, height, format);
Texture* texture = lovrTextureCreate(TEXTURE_2D, &textureData, 1, true);
Texture* texture = lovrTextureCreate(TEXTURE_2D, &textureData, 1, true, true);
if (!texture) return NULL;
Canvas* canvas = lovrAlloc(sizeof(Canvas), lovrCanvasDestroy);

View File

@ -324,7 +324,7 @@ void lovrFontCreateTexture(Font* font) {
TextureData* textureData = lovrTextureDataGetBlank(font->atlas.width, font->atlas.height, 0x0, FORMAT_RGB);
TextureFilter filter = { .mode = FILTER_BILINEAR };
font->texture = lovrTextureCreate(TEXTURE_2D, &textureData, 1, false);
font->texture = lovrTextureCreate(TEXTURE_2D, &textureData, 1, false, false);
lovrTextureSetFilter(font->texture, filter);
lovrTextureSetWrap(font->texture, (TextureWrap) { .s = WRAP_CLAMP, .t = WRAP_CLAMP });
}

View File

@ -1136,6 +1136,8 @@ void lovrGraphicsSkybox(Texture* texture, float angle, float ax, float ay, float
lovrMaterialSetTexture(material, TEXTURE_DIFFUSE, texture);
lovrGraphicsSphere(material, NULL, 30);
lovrMaterialSetTexture(material, TEXTURE_DIFFUSE, NULL);
} else {
lovrThrow("lovr.graphics.skybox only accepts 2d and cube Textures");
}
lovrGraphicsSetDepthTest(mode, write);
@ -1234,7 +1236,7 @@ void lovrGraphicsBindTexture(Texture* texture, TextureType type, int slot) {
if (!texture) {
if (!state.defaultTexture) {
TextureData* textureData = lovrTextureDataGetBlank(1, 1, 0xff, FORMAT_RGBA);
state.defaultTexture = lovrTextureCreate(TEXTURE_2D, &textureData, 1, true);
state.defaultTexture = lovrTextureCreate(TEXTURE_2D, &textureData, 1, true, false);
}
texture = state.defaultTexture;

View File

@ -70,7 +70,7 @@ Model* lovrModelCreate(ModelData* modelData) {
model->textures = malloc(modelData->textures.length * sizeof(Texture*));
for (int i = 0; i < modelData->textures.length; i++) {
if (modelData->textures.data[i]) {
model->textures[i] = lovrTextureCreate(TEXTURE_2D, (TextureData**) &modelData->textures.data[i], 1, true);
model->textures[i] = lovrTextureCreate(TEXTURE_2D, (TextureData**) &modelData->textures.data[i], 1, true, true);
} else {
model->textures[i] = NULL;
}

View File

@ -26,6 +26,7 @@ static UniformType getUniformType(GLenum type, const char* debug) {
case GL_SAMPLER_2D:
case GL_SAMPLER_CUBE:
case GL_SAMPLER_2D_ARRAY:
return UNIFORM_SAMPLER;
default:
@ -40,6 +41,7 @@ static int getUniformComponents(GLenum type) {
case GL_INT:
case GL_SAMPLER_2D:
case GL_SAMPLER_CUBE:
case GL_SAMPLER_2D_ARRAY:
return 1;
case GL_FLOAT_VEC2:
@ -303,7 +305,12 @@ void lovrShaderBind(Shader* shader) {
case UNIFORM_SAMPLER:
for (int i = 0; i < count; i++) {
TextureType type = uniform->glType == GL_SAMPLER_2D ? TEXTURE_2D : TEXTURE_CUBE;
TextureType type;
switch (uniform->glType) {
case GL_SAMPLER_2D: type = TEXTURE_2D; break;
case GL_SAMPLER_CUBE: type = TEXTURE_CUBE; break;
case GL_SAMPLER_2D_ARRAY: type = TEXTURE_ARRAY; break;
}
lovrGraphicsBindTexture(uniform->value.textures[i], type, uniform->baseTextureSlot + i);
}
break;

View File

@ -31,113 +31,66 @@ GLenum lovrTextureFormatGetGLInternalFormat(TextureFormat format, bool srgb) {
}
bool lovrTextureFormatIsCompressed(TextureFormat format) {
switch (format) {
case FORMAT_DXT1:
case FORMAT_DXT3:
case FORMAT_DXT5:
return true;
default:
return false;
}
return format == FORMAT_DXT1 || format == FORMAT_DXT3 || format == FORMAT_DXT5;
}
static void lovrTextureUpload(Texture* texture) {
TextureData* textureData = texture->slices[0];
static void lovrTextureAllocate(Texture* texture, TextureData* textureData) {
texture->allocated = true;
texture->width = textureData->width;
texture->height = textureData->height;
if (lovrTextureFormatIsCompressed(textureData->format)) {
return;
}
int w = textureData->width;
int h = textureData->height;
int mipmapCount = log2(MAX(w, h)) + 1;
bool srgb = lovrGraphicsIsGammaCorrect() && texture->srgb;
// Allocate storage
if (!lovrTextureFormatIsCompressed(textureData->format) && texture->type != TEXTURE_CUBE) {
int w = textureData->width;
int h = textureData->height;
int mipmapCount = log2(MAX(w, h)) + 1;
GLenum glFormat = lovrTextureFormatGetGLFormat(textureData->format);
GLenum internalFormat = lovrTextureFormatGetGLInternalFormat(textureData->format, srgb);
GLenum glFormat = lovrTextureFormatGetGLFormat(textureData->format);
GLenum internalFormat = lovrTextureFormatGetGLInternalFormat(textureData->format, srgb);
#ifndef EMSCRIPTEN
if (GLAD_GL_ARB_texture_storage) {
if (GLAD_GL_ARB_texture_storage) {
#endif
glTexStorage2D(GL_TEXTURE_2D, mipmapCount, internalFormat, w, h);
glTexStorage2D(texture->type, mipmapCount, internalFormat, w, h);
#ifndef EMSCRIPTEN
} else {
for (int i = 0; i < mipmapCount; i++) {
glTexImage2D(GL_TEXTURE_2D, i, internalFormat, w, h, 0, glFormat, GL_UNSIGNED_BYTE, NULL);
w = MAX(w >> 1, 1);
h = MAX(h >> 1, 1);
}
}
#endif
}
// Upload data
for (int i = 0; i < texture->sliceCount; i++) {
TextureData* textureData = texture->slices[i];
GLenum glFormat = lovrTextureFormatGetGLFormat(textureData->format);
GLenum glInternalFormat = lovrTextureFormatGetGLInternalFormat(textureData->format, srgb);
GLenum binding = (texture->type == TEXTURE_CUBE) ? GL_TEXTURE_CUBE_MAP_POSITIVE_X + i : GL_TEXTURE_2D;
if (lovrTextureFormatIsCompressed(textureData->format)) {
Mipmap m; int i;
vec_foreach(&textureData->mipmaps, m, i) {
glCompressedTexImage2D(binding, i, glInternalFormat, m.width, m.height, 0, m.size, m.data);
}
} else {
int w = textureData->width;
int h = textureData->height;
if (texture->type == TEXTURE_CUBE) {
glTexImage2D(binding, 0, glInternalFormat, w, h, 0, glFormat, GL_UNSIGNED_BYTE, textureData->data);
} else if (textureData->data) {
glTexSubImage2D(binding, 0, 0, 0, w, h, glFormat, GL_UNSIGNED_BYTE, textureData->data);
}
if (textureData->generateMipmaps) {
glGenerateMipmap(texture->type);
}
}
}
}
static void validateSlices(TextureType type, TextureData* slices[6], int sliceCount) {
lovrAssert(sliceCount > 0, "At least one layer must be provided to create a texture");
int maxSize = lovrGraphicsGetLimits().textureSize;
int width = slices[0]->width;
int height = slices[0]->height;
bool oversized = width > maxSize || height > maxSize;
lovrAssert(!oversized, "Texture is too big, max size is %d x %d", maxSize, maxSize);
if (type == TEXTURE_CUBE) {
lovrAssert(sliceCount == 6, "Cube textures must have 6 images");
lovrAssert(width == height, "Cube textures must be square");
for (int i = 1; i < sliceCount; i++) {
int hasSameDimensions = slices[i]->width == width && slices[i]->height == height;
lovrAssert(hasSameDimensions, "All textures in a cube texture must have the same dimensions");
}
} else if (type == TEXTURE_2D) {
lovrAssert(sliceCount == 1, "2D textures can only contain a single image");
} else {
lovrThrow("Unknown texture type");
for (int i = 0; i < mipmapCount; i++) {
if (texture->type == TEXTURE_CUBE) {
for (int face = 0; face < 6; face++) {
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, i, internalFormat, w, h, 0, glFormat, GL_UNSIGNED_BYTE, NULL);
}
} else {
glTexImage2D(texture->type, i, internalFormat, w, h, 0, glFormat, GL_UNSIGNED_BYTE, NULL);
}
w = MAX(w >> 1, 1);
h = MAX(h >> 1, 1);
}
}
#endif
}
Texture* lovrTextureCreate(TextureType type, TextureData* slices[6], int sliceCount, bool srgb) {
Texture* lovrTextureCreate(TextureType type, TextureData** slices, int sliceCount, bool srgb, bool mipmaps) {
Texture* texture = lovrAlloc(sizeof(Texture), lovrTextureDestroy);
if (!texture) return NULL;
texture->type = type;
validateSlices(type, slices, sliceCount);
texture->slices = calloc(sliceCount, sizeof(TextureData**));
texture->sliceCount = sliceCount;
memcpy(texture->slices, slices, sliceCount * sizeof(TextureData*));
texture->srgb = srgb;
glGenTextures(1, &texture->id);
lovrGraphicsBindTexture(texture, type, 0);
lovrTextureUpload(texture);
lovrTextureSetFilter(texture, lovrGraphicsGetDefaultFilter());
WrapMode wrapMode = (type == TEXTURE_CUBE) ? WRAP_CLAMP : WRAP_REPEAT;
lovrTextureSetWrap(texture, (TextureWrap) { .s = wrapMode, .t = wrapMode, .r = wrapMode });
texture->mipmaps = mipmaps;
texture->allocated = false;
for (int i = 0; i < sliceCount; i++) {
lovrRetain(&slices[i]->ref);
WrapMode wrap = type == TEXTURE_CUBE ? WRAP_CLAMP : WRAP_REPEAT;
glGenTextures(1, &texture->id);
lovrGraphicsBindTexture(texture, texture->type, 0);
lovrTextureSetFilter(texture, lovrGraphicsGetDefaultFilter());
lovrTextureSetWrap(texture, (TextureWrap) { .s = wrap, .t = wrap, .r = wrap });
if (slices) {
for (int i = 0; i < sliceCount; i++) {
lovrTextureReplacePixels(texture, slices[i], i);
}
}
return texture;
@ -146,17 +99,44 @@ Texture* lovrTextureCreate(TextureType type, TextureData* slices[6], int sliceCo
void lovrTextureDestroy(const Ref* ref) {
Texture* texture = containerof(ref, Texture);
for (int i = 0; i < texture->sliceCount; i++) {
lovrRelease(&texture->slices[i]->ref);
if (&texture->slices[i]) {
lovrRelease(&texture->slices[i]->ref);
}
}
glDeleteTextures(1, &texture->id);
free(texture->slices);
free(texture);
}
void lovrTextureReplacePixels(Texture* texture, TextureData* textureData, int slice) {
lovrAssert(slice >= 0 && slice < texture->sliceCount, "Invalid texture slice to replace: %d", slice);
lovrAssert(textureData->width == texture->width && textureData->height == texture->height, "Texture dimensions must match");
lovrAssert(textureData->format == texture->slices[0]->format, "Texture formats must match");
lovrTextureUpload(texture);
lovrRetain(&textureData->ref);
if (texture->slices[slice]) {
lovrRelease(&texture->slices[slice]->ref);
}
texture->slices[slice] = textureData;
if (!texture->allocated) {
lovrTextureAllocate(texture, textureData);
} else {
// validation
}
GLenum glFormat = lovrTextureFormatGetGLFormat(textureData->format);
GLenum glInternalFormat = lovrTextureFormatGetGLInternalFormat(textureData->format, texture->srgb);
GLenum binding = (texture->type == TEXTURE_CUBE) ? GL_TEXTURE_CUBE_MAP_POSITIVE_X + slice : texture->type;
if (lovrTextureFormatIsCompressed(textureData->format)) {
Mipmap m; int i;
vec_foreach(&textureData->mipmaps, m, i) {
glCompressedTexImage2D(binding, i, glInternalFormat, m.width, m.height, 0, m.size, m.data);
}
} else {
glTexSubImage2D(binding, 0, 0, 0, textureData->width, textureData->height, glFormat, GL_UNSIGNED_BYTE, textureData->data);
if (texture->mipmaps) {
glGenerateMipmap(texture->type);
}
}
}
TextureFilter lovrTextureGetFilter(Texture* texture) {
@ -164,7 +144,6 @@ TextureFilter lovrTextureGetFilter(Texture* texture) {
}
void lovrTextureSetFilter(Texture* texture, TextureFilter filter) {
bool hasMipmaps = lovrTextureFormatIsCompressed(texture->slices[0]->format) || texture->slices[0]->generateMipmaps;
float anisotropy = filter.mode == FILTER_ANISOTROPIC ? MAX(filter.anisotropy, 1.) : 1.;
lovrGraphicsBindTexture(texture, texture->type, 0);
texture->filter = filter;
@ -176,7 +155,7 @@ void lovrTextureSetFilter(Texture* texture, TextureFilter filter) {
break;
case FILTER_BILINEAR:
if (hasMipmaps) {
if (texture->mipmaps) {
glTexParameteri(texture->type, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
glTexParameteri(texture->type, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
} else {
@ -187,7 +166,7 @@ void lovrTextureSetFilter(Texture* texture, TextureFilter filter) {
case FILTER_TRILINEAR:
case FILTER_ANISOTROPIC:
if (hasMipmaps) {
if (texture->mipmaps) {
glTexParameteri(texture->type, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(texture->type, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
} else {

View File

@ -6,6 +6,7 @@
typedef enum {
TEXTURE_2D = GL_TEXTURE_2D,
TEXTURE_ARRAY = GL_TEXTURE_2D_ARRAY,
TEXTURE_CUBE = GL_TEXTURE_CUBE_MAP
} TextureType;
@ -36,7 +37,7 @@ typedef struct {
typedef struct {
Ref ref;
TextureType type;
TextureData* slices[6];
TextureData** slices;
int sliceCount;
int width;
int height;
@ -44,13 +45,15 @@ typedef struct {
TextureFilter filter;
TextureWrap wrap;
bool srgb;
bool mipmaps;
bool allocated;
} Texture;
GLenum lovrTextureFormatGetGLFormat(TextureFormat format);
GLenum lovrTextureFormatGetGLInternalFormat(TextureFormat format, bool srgb);
bool lovrTextureFormatIsCompressed(TextureFormat format);
Texture* lovrTextureCreate(TextureType type, TextureData* data[6], int count, bool srgb);
Texture* lovrTextureCreate(TextureType type, TextureData** slices, int count, bool srgb, bool mipmaps);
void lovrTextureDestroy(const Ref* ref);
void lovrTextureReplacePixels(Texture* texture, TextureData* data, int slice);
TextureFilter lovrTextureGetFilter(Texture* texture);

View File

@ -713,7 +713,6 @@ static ModelData* openvrControllerNewModelData(Controller* controller) {
textureData->format = FORMAT_RGBA;
textureData->data = memcpy(malloc(size), vrTexture->rubTextureMapData, size);
textureData->blob = NULL;
textureData->generateMipmaps = true;
vec_init(&textureData->mipmaps);
modelData->materials[0].diffuseColor = (Color) { 1, 1, 1, 1 };

View File

@ -106,6 +106,7 @@ const char* lovrSkyboxVertexShader = ""
"out vec3 texturePosition; \n"
"vec4 position(mat4 projection, mat4 transform, vec4 vertex) { \n"
" texturePosition = vertex.xyz; \n"
" texturePosition.y *= -1; \n"
" return projection * transform * vertex; \n"
"}";