Files
fs2open.github.com/code/graphics/opengl/gropengltexture.cpp
the-eandClaude Opus 5 019c4bacea Fix texture filtering silently defaulting to bilinear
opengl_tcache_init() read GL_mipmap_filter out of TextureFilteringOption
before seeding it from the legacy config key. That option's default_func
returns GL_mipmap_filter itself, so with no persisted "Graphics.TextureFilter"
value getValue() fell through to a still-zero-initialized global -- bilinear,
where the config default is trilinear. Since in-game options are on by default,
that affected every player who had never explicitly set the option, and it
ignored the legacy TextureFilter key existing installs were configured through.

Seed from config first, then let the option override, matching the order the
anisotropy setting below already uses (its default_func queries the hardware
directly, which is why it was never affected).

Also route the option's value enumerator through the new shared
gr_get_supported_anisotropy_levels() rather than a private copy.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-11-07 19:45:48 +01:00

2166 lines
64 KiB
C++

/*
* Copyright (C) Volition, Inc. 1999. All rights reserved.
*
* All source code herein is the property of Volition, Inc. You may not sell
* or otherwise commercially exploit the source or things you created based on the
* source.
*
*/
#ifdef _WIN32
#include <windows.h>
#endif
#define BMPMAN_INTERNAL
#include "gropenglstate.h"
#include "gropengltexture.h"
#include "gropengldraw.h"
#include "gropenglpostprocessing.h"
#include "gropengltnl.h"
#include "bmpman/bm_internal.h"
#include "bmpman/bmpman.h"
#include "cmdline/cmdline.h"
#include "ddsutils/ddsutils.h"
#include "globalincs/systemvars.h"
#include "graphics/2d.h"
#include "graphics/grinternal.h"
#include "math/vecmat.h"
#include "options/Option.h"
#include "osapi/osregistry.h"
#include "ktxutils/ktxutils.h"
#ifdef USE_OPENGL_ES
#include "es_compatibility.h"
#endif
matrix4 GL_texture_matrix;
int GL_texture_ram = 0;
int GL_min_texture_width = 0;
GLint GL_max_texture_width = 0;
int GL_min_texture_height = 0;
GLint GL_max_texture_height = 0;
int GL_textures_in_frame = 0;
int GL_last_detail = -1;
GLint GL_supported_texture_units = 2;
int GL_should_preload = 0;
GLfloat GL_anisotropy = 1.0f;
int GL_mipmap_filter = 0;
GLenum GL_texture_target = GL_TEXTURE_2D;
GLenum GL_texture_face = GL_TEXTURE_2D;
GLenum GL_texture_addressing = GL_REPEAT;
bool GL_rendering_to_texture = false;
GLint GL_max_renderbuffer_size = 0;
extern int GLOWMAP;
extern int SPECMAP;
extern int ENVMAP;
const SCP_vector<std::pair<int, std::pair<const char*, int>>> TextureFilteringValues = {{0, {"Bilinear", 1677}},
{ 1, {"Trilinear", 1678}}};
static void parse_texture_filtering_func()
{
SCP_string mode;
stuff_string(mode, F_NAME);
// Convert to lowercase once
SCP_tolower(mode);
// Use a map to associate strings with their respective actions
static const std::unordered_map<std::string, std::function<void()>> effectActions = {
{"bilinear", []() { GL_mipmap_filter = 0; }},
{"trilinear", []() { GL_mipmap_filter = 1; }}};
auto it = effectActions.find(mode);
if (it != effectActions.end()) {
it->second(); // Execute the corresponding action
} else {
error_display(0, "%s is not a valid texturing filtering setting", mode.c_str());
}
}
// coverity[GLOBAL_INIT_ORDER] -- safe; OptionBuilder::finish() uses Meyers singleton
static auto TextureFilteringOption __UNUSED = options::OptionBuilder<int>("Graphics.TextureFilter",
std::pair<const char*, int>{"Texture Filtering", 1763},
std::pair<const char*, int>{"Texture filtering option", 1764})
.importance(1)
.category(std::make_pair("Graphics", 1825))
.values(TextureFilteringValues)
.default_func([]() {return GL_mipmap_filter;})
.bind_to_once(&GL_mipmap_filter)
.flags({options::OptionFlags::ForceMultiValueSelection})
.parser(parse_texture_filtering_func)
.finish();
static SCP_string anisotropic_display(float val)
{
if (val < 2.0f) {
return "None";
}
SCP_string out;
sprintf(out, "%.0fx", val);
return out;
}
static float anisotropic_default()
{
float max;
if (!gr_get_property(gr_property::MAX_ANISOTROPY, &max)) {
return 1.0f;
}
return max;
}
// coverity[GLOBAL_INIT_ORDER] -- safe; OptionBuilder::finish() uses Meyers singleton
static auto AnisotropyOption = options::OptionBuilder<float>("Graphics.Anisotropy",
std::pair<const char*, int>{"Anistropic filtering", 1736},
std::pair<const char*, int>{"Controls the amount of anistropic filtering of the textures", 1737})
.enumerator(gr_get_supported_anisotropy_levels)
.category(std::make_pair("Graphics", 1825))
.display(anisotropic_display)
.default_func(anisotropic_default)
.level(options::ExpertLevel::Advanced)
.importance(78)
.finish();
// forward declarations
int opengl_free_texture(tcache_slot_opengl *t);
int opengl_create_texture (int bitmap_handle, int bitmap_type, tcache_slot_opengl *tslot = NULL);
// Running total of the bytes of texture data held by all live texture cache slots, for the
// profiler overlay's memory panel. Kept in sync at every site that fills or drops a slot's data
// rather than summed on demand, because the slots live in bmpman's blocks and a full walk of them
// each frame is too expensive. Animations put all their frames in one texture array but give each
// frame its own slot, so freeing some frames of an animation lowers this total while the array
// itself stays alive until the last frame goes away.
static size_t GL_texture_bytes_used = 0;
/**
* @brief Removes a texture cache slot's bytes from GL_texture_bytes_used
*
* Call this at each site that drops the texture data of a slot, immediately before the code that
* clears the slot. The function sets the size of the slot to 0, thus a second call for the same
* slot does nothing.
*/
static void opengl_texture_release_bytes(tcache_slot_opengl* t)
{
GL_texture_bytes_used -= static_cast<size_t>(t->size);
t->size = 0;
}
extern int get_num_mipmap_levels(int w, int h);
void opengl_set_additive_tex_env()
{
GL_CHECK_FOR_ERRORS("start of set_additive_tex_env()");
GL_CHECK_FOR_ERRORS("end of set_additive_tex_env()");
}
void opengl_set_modulate_tex_env()
{
GL_CHECK_FOR_ERRORS("start of set_modulate_tex_env()");
GL_CHECK_FOR_ERRORS("end of set_modulate_tex_env()");
}
void opengl_set_texture_target( GLenum target )
{
GL_texture_target = target;
}
void opengl_tcache_init()
{
GL_should_preload = 1;
GL_min_texture_width = 16;
GL_min_texture_height = 16;
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &GL_max_texture_width);
GL_max_texture_height = GL_max_texture_width;
// if we are not using sections then make sure we have the min texture size available to us
// 1024 is what we need with the standard resolutions - taylor
if (GL_max_texture_width < 1024) {
Error(LOCATION, "A minimum texture size of \"1024x1024\" is required for FS2_Open but only \"%ix%i\" was found. Can not continue.", GL_max_texture_width, GL_max_texture_height);
}
// check what mipmap filter we should be using
// 0 == Bilinear
// 1 == Trilinear
// Seed from the legacy config key first: TextureFilteringOption's default_func returns
// GL_mipmap_filter, so this read is what supplies that default. Only then let the option
// override it, the same order the anisotropy setting below uses.
GL_mipmap_filter = os_config_read_uint(NULL, "TextureFilter", 1);
if (Using_in_game_options) {
GL_mipmap_filter = TextureFilteringOption->getValue();
}
if (GL_mipmap_filter > 1) {
GL_mipmap_filter = 1;
}
// max size (width and/or height) that we can use for framebuffer/renderbuffer
glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &GL_max_renderbuffer_size);
// if we can't do at least 128x128 then just disable FBOs
if (GL_max_renderbuffer_size < 128) {
mprintf(("WARNING: Max dimensions of FBO, %ix%i, is less the required minimum!! Extension will be disabled!\n", GL_max_renderbuffer_size, GL_max_renderbuffer_size));
Cmdline_no_fbo = 1;
}
// anisotropy
if ( GLAD_GL_EXT_texture_filter_anisotropic ) {
// set max value first thing
GL_anisotropy = GL_state.Constants.GetMaxAnisotropy();
if (Using_in_game_options) {
GL_anisotropy = AnisotropyOption->getValue();
} else {
// now for the user setting
if (Cmdline_aniso_level != 0)
GL_anisotropy = (GLfloat)Cmdline_aniso_level;
}
CLAMP(GL_anisotropy, 1.0f, GL_state.Constants.GetMaxAnisotropy());
}
Assert( GL_supported_texture_units >= 2 );
GL_last_detail = Detail.hardware_textures;
GL_textures_in_frame = 0;
vm_matrix4_set_identity(&GL_texture_matrix);
}
void opengl_tcache_flush()
{
for (auto& block : bm_blocks) {
for (auto& slot : block) {
opengl_free_texture(static_cast<tcache_slot_opengl*>(slot.gr_info));
}
}
// A flush releases the texture of every slot, thus the total must go to 0. A value that is not
// 0 shows that a site which drops texture data does not call opengl_texture_release_bytes().
// The shutdown sequence is the one exception: bm_close() removes the blocks before it closes
// the graphics backend, thus the loop above then finds no slot to release.
Assertion(bm_blocks.empty() || (GL_texture_bytes_used == 0),
"OpenGL texture memory tracking lost " SIZE_T_ARG " bytes. A texture release site is missing.",
GL_texture_bytes_used);
}
extern void opengl_kill_all_render_targets();
void opengl_tcache_shutdown()
{
opengl_kill_all_render_targets();
opengl_tcache_flush();
GL_textures_in_frame = 0;
}
void opengl_tcache_frame()
{
GL_textures_in_frame = 0;
}
extern bool GL_initted;
void opengl_free_texture_slot(bitmap_slot* slot)
{
if ( !GL_initted ) {
return;
}
auto tcache_slot = static_cast<tcache_slot_opengl*>(slot->gr_info);
if (tcache_slot == nullptr) {
// This slot hasn't been initialized yet and can be skipped safely
return;
}
opengl_free_texture(tcache_slot);
}
/**
* Determine if a bitmap is in API memory, so that we can just reuse it rather
* that having to load it from disk again
*/
bool opengl_texture_slot_valid(int handle)
{
auto t = bm_get_gr_info<tcache_slot_opengl>(handle);
if (t->bitmap_handle < 0) {
return false;
}
if (t->bitmap_handle != handle) {
return false;
}
if ( !glIsTexture(t->texture_id) ) {
return false;
}
return true;
}
int opengl_free_texture(tcache_slot_opengl *t)
{
if (!t->texture_id) {
// No texture here anymore
return 1;
}
Assertion(t->used, "Tried to free unused texture slot. This shouldn't happen!");
// First mark this as unused and then check if all frames are unused. If that's the case we can free the texture array
t->used = false;
// Check if the bitmap handle is valid
if (t->bitmap_handle >= 0) {
int num_frames = 0;
auto animation_begin = bm_get_base_frame(t->bitmap_handle, &num_frames);
if (animation_begin < 0) {
// Some kind of error
return 0;
}
// Get the index of the first slot and then check every slot of the animation
auto end_slot_handle = animation_begin + num_frames - 1; // Slots are contiguous for the same animation. Since num_frames is 1-base we need to substract 1
bool something_in_use = false; // Will be used to check if there is still a frame left in use
for (int slot_handle = animation_begin; slot_handle <= end_slot_handle; ++slot_handle) {
auto current_slot = bm_get_gr_info<tcache_slot_opengl>(slot_handle);
something_in_use = something_in_use || current_slot->used; // We use or here since only one slot needs to be in use
if (something_in_use) {
// If this is true then we can break early since we already determined that one frame is in use
break;
}
}
if (something_in_use) {
// There are still used frames left in the animation so we can't free the texture object here
// We still need to reset this slot though since it has been "freed"
opengl_texture_release_bytes(t);
t->reset();
// Everything is fine
return 1;
}
// No frame is used anymore so now we can free the texture
} else {
// If we are here then the texture slot must contain an FBO texture
Assertion(t->fbo_id >= 0, "Found texture slot with invalid bitmap number which wasn't an FBO!");
}
GR_DEBUG_SCOPE("Delete texture");
// ok, now we know its legal to free everything safely
GL_state.Texture.Delete(t->texture_id);
glDeleteTextures (1, &t->texture_id);
// Finally, reset the data of this slot
opengl_texture_release_bytes(t);
t->reset();
return 1;
}
// data == start of bitmap data
// bmap_w == width of source bitmap
// bmap_h == height of source bitmap
// tex_w == width of final texture
// tex_h == height of final texture
static int opengl_texture_set_level(int bitmap_handle, int bitmap_type, int bmap_w, int bmap_h, int tex_w, int tex_h, int tex_d,
ubyte* data, tcache_slot_opengl* tSlot, int base_level, int mipmap_levels,
bool resize, GLenum intFormat) {
GR_DEBUG_SCOPE("Upload single frame");
int ret_val = 1;
ubyte* bmp_data = data;
ubyte *texmem = NULL, *texmemp = NULL;
int skip_size = 0;
GL_CHECK_FOR_ERRORS("start of create_texture_sub()");
// bogus
if ((tSlot == nullptr) || (bmp_data == nullptr)) {
return 0;
}
if ((bitmap_type == TCACHE_TYPE_AABITMAP) || (bitmap_type == TCACHE_TYPE_INTERFACE)) {
tSlot->u_scale = (float)bmap_w / (float)tex_w;
tSlot->v_scale = (float)bmap_h / (float)tex_h;
} else {
tSlot->u_scale = 1.0f;
tSlot->v_scale = 1.0f;
}
// set the byte per pixel multiplier
auto byte_mult = (tSlot->bpp >> 3);
GLenum texFormat = GL_UNSIGNED_SHORT_1_5_5_5_REV;
GLenum glFormat = GL_BGRA;
// GL_BGRA_EXT is *much* faster with some hardware/drivers
if (byte_mult == 4) {
texFormat = GL_UNSIGNED_INT_8_8_8_8_REV;
intFormat = (gr_screen.bits_per_pixel == 32) ? GL_RGBA8 : GL_RGB5_A1;
glFormat = GL_BGRA;
} else if (byte_mult == 3) {
texFormat = GL_UNSIGNED_BYTE;
intFormat = (gr_screen.bits_per_pixel == 32) ? GL_RGB8 : GL_RGB5;
glFormat = GL_BGR;
} else if (byte_mult == 2) {
texFormat = GL_UNSIGNED_SHORT_1_5_5_5_REV;
intFormat = GL_RGB5_A1;
glFormat = GL_BGRA;
} else if (byte_mult == 1) {
Assertion(bitmap_type == TCACHE_TYPE_AABITMAP,
"Invalid type for bitmap: %s BMPMAN handle: %d. Type expected was 0, we got %d instead.\nThis can be "
"caused by using texture compression on a non-power-of-2 texture.\n",
bm_get_filename(bitmap_handle), bitmap_handle, bitmap_type);
texFormat = GL_UNSIGNED_BYTE;
intFormat = GL_RED;
glFormat = GL_RED;
} else {
texFormat = GL_UNSIGNED_SHORT_1_5_5_5_REV;
intFormat = GL_RGBA;
glFormat = GL_BGRA;
}
// check for compressed image types
auto bm_type = bm_is_compressed(bitmap_handle);
auto block_size = dds_block_size(bm_type);
switch (bm_type) {
case DDS_DXT1:
case DDS_CUBEMAP_DXT1:
intFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
break;
case DDS_DXT3:
case DDS_CUBEMAP_DXT3:
intFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
break;
case DDS_DXT5:
case DDS_CUBEMAP_DXT5:
intFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
break;
case DDS_BC7:
intFormat = GL_COMPRESSED_RGBA_BPTC_UNORM_ARB;
break;
case KTX_ETC2_RGB:
case KTX_ETC2_RGBA_EAC:
case KTX_ETC2_SRGB:
case KTX_ETC2_SRGBA_EAC:
case KTX_ETC2_RGB_A1:
case KTX_ETC2_SRGB_A1:
intFormat = ktx_map_ktx_format_to_gl_internal(bm_type);
block_size = ktx_etc_block_size(intFormat);
break;
}
if (bitmap_type == TCACHE_TYPE_CUBEMAP) {
tSlot->texture_target = GL_TEXTURE_CUBE_MAP;
}
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
auto dsize = 0;
auto doffset = 0;
switch (bitmap_type) {
case TCACHE_TYPE_COMPRESSED: {
if (block_size > 0) {
auto mipmap_w = bmap_w;
auto mipmap_h = bmap_h;
for (auto i = 0; i < mipmap_levels + base_level; i++) {
dsize = static_cast<GLsizei>(dds_compressed_mip_size(mipmap_w, mipmap_h, block_size));
if (i >= base_level) {
glCompressedTexSubImage3D(tSlot->texture_target, i - base_level, 0, 0, tSlot->array_index, mipmap_w,
mipmap_h, 1, intFormat, dsize, bmp_data + doffset);
}
// adjust the data offset for the next block
doffset += dsize;
// reduce size by half for the next pass
mipmap_w >>= 1;
mipmap_h >>= 1;
if (mipmap_w <= 0) {
mipmap_w = 1;
}
if (mipmap_h <= 0) {
mipmap_h = 1;
}
}
} else {
Int3();
return 0;
}
break;
}
case TCACHE_TYPE_AABITMAP: {
texmem = (ubyte*)vm_malloc(tex_w * tex_h * byte_mult);
texmemp = texmem;
Assert(texmem != nullptr);
int luminance = 0;
for (auto i = 0; i < tex_h; i++) {
for (auto j = 0; j < tex_w; j++) {
if ((i < bmap_h) && (j < bmap_w)) {
if (byte_mult > 1) {
luminance = 0;
if (byte_mult > 3) {
for (auto k = 0; k < 3; k++) {
luminance += bmp_data[(i * bmap_w + j) * byte_mult + k];
}
*texmemp++ =
(ubyte)((luminance / 3) * (bmp_data[(i * bmap_w + j) * byte_mult + 3] / 255.0f));
} else {
for (auto k = 0; k < byte_mult; k++) {
luminance += bmp_data[(i * bmap_w + j) * byte_mult + k];
}
*texmemp++ = (ubyte)(luminance / byte_mult);
}
} else {
*texmemp++ = bmp_data[i * bmap_w + j];
}
} else {
*texmemp++ = 0;
}
}
}
GLenum aa_format = GL_RED;
glTexSubImage3D(tSlot->texture_target, 0, 0, 0, tSlot->array_index, tex_w, tex_h, 1, aa_format,
GL_UNSIGNED_BYTE, texmem);
if (texmem != nullptr) {
vm_free(texmem);
}
break;
}
case TCACHE_TYPE_INTERFACE: {
// if we aren't resizing then we can just use bmp_data directly
if (resize) {
texmem = (ubyte*)vm_malloc(tex_w * tex_h * byte_mult);
texmemp = texmem;
Assert(texmem != nullptr);
for (auto i = 0; i < tex_h; i++) {
for (auto j = 0; j < tex_w; j++) {
if ((i < bmap_h) && (j < bmap_w)) {
for (auto k = 0; k < byte_mult; k++) {
*texmemp++ = bmp_data[(i * bmap_w + j) * byte_mult + k];
}
} else {
for (auto k = 0; k < byte_mult; k++) {
*texmemp++ = 0;
}
}
}
}
}
glTexSubImage3D(tSlot->texture_target, 0, 0, 0, tSlot->array_index, tex_w, tex_h, 1, glFormat, texFormat,
(resize) ? texmem : bmp_data);
if (texmem != nullptr) {
vm_free(texmem);
}
break;
}
case TCACHE_TYPE_CUBEMAP: {
Assert(!resize);
Assert(texmem == nullptr);
// we have to load in all 6 faces...
doffset = 0;
for (auto i = 0; i < 6; i++) {
// We need to use the actual size of the bitmap here since tex_h/w is already
// adjusted for the size of the target bitmap. Since we are not resizing here that is not a problem
auto mipmap_w = bmap_w;
auto mipmap_h = bmap_h;
// check if it's a compressed cubemap first
if (block_size > 0) {
for (auto level = 0; level < mipmap_levels + base_level; level++) {
dsize = static_cast<GLsizei>(dds_compressed_mip_size(mipmap_w, mipmap_h, block_size));
if (level >= base_level) {
// We skipped ahead to the base level so we can start uploading frames now
glCompressedTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, level - base_level, 0, 0,
mipmap_w, mipmap_h, intFormat, dsize, bmp_data + doffset);
}
// adjust the data offset for the next block
doffset += dsize;
// reduce size by half for the next pass
mipmap_w >>= 1;
mipmap_h >>= 1;
if (mipmap_w <= 0) {
mipmap_w = 1;
}
if (mipmap_h <= 0) {
mipmap_h = 1;
}
}
} else {
// nope, it's uncompressed...
for (auto level = 0; level < mipmap_levels + base_level; level++) {
dsize = mipmap_h * mipmap_w * byte_mult;
if (level >= base_level) {
glTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, level - base_level, 0, 0, mipmap_w,
mipmap_h, glFormat, texFormat, bmp_data + doffset);
}
// base image is done so now take care of any mipmap levels
doffset += dsize;
mipmap_w >>= 1;
mipmap_h >>= 1;
if (mipmap_w <= 0) {
mipmap_w = 1;
}
if (mipmap_h <= 0) {
mipmap_h = 1;
}
}
}
}
break;
}
case TCACHE_TYPE_3DTEX:
//Just upload as-is.
glTexSubImage3D(tSlot->texture_target, 0, 0, 0, 0, tex_w, tex_h, tex_d,
glFormat, texFormat, bmp_data + doffset);
break;
default: {
// if we aren't resizing then we can just use bmp_data directly
if (resize) {
texmem = (ubyte*)vm_malloc(tex_w * tex_h * byte_mult);
texmemp = texmem;
Assert(texmem != nullptr);
fix u, utmp, v, du, dv;
u = v = 0;
du = ((bmap_w - 1) * F1_0) / tex_w;
dv = ((bmap_h - 1) * F1_0) / tex_h;
for (auto j = 0; j < tex_h; j++) {
utmp = u;
for (auto i = 0; i < tex_w; i++) {
for (auto k = 0; k < byte_mult; k++) {
*texmemp++ = bmp_data[(f2i(v) * bmap_w + f2i(utmp)) * byte_mult + k];
}
utmp += du;
}
v += dv;
}
}
auto mipmap_w = tex_w;
auto mipmap_h = tex_h;
// if we are doing mipmap resizing we need to account for adjusted tex size
// (we can end up with only one mipmap level if base_level is high enough so don't check it)
if (base_level > 0) {
Assertion(resize == false, "Cannot use manual and mipmap resizing at the same time!");
Assert(texmem == nullptr);
// If we have mipmaps then tex_w/h are already adjusted for the base level but that will cause problems with
// the code below. Instead we set the values to the actual size of the bitmap here to make sure we can
// iterate properly through the mipmap levels.
mipmap_w = bmap_w;
mipmap_h = bmap_h;
}
for (auto i = 0; i < mipmap_levels + base_level; i++) {
// size of data block (4x4)
dsize = mipmap_h * mipmap_w * byte_mult;
if (i >= base_level) {
glTexSubImage3D(tSlot->texture_target, i - base_level, 0, 0, tSlot->array_index, mipmap_w, mipmap_h, 1,
glFormat, texFormat, (texmem != nullptr) ? texmem : bmp_data + doffset);
}
// adjust the data offset for the next block
doffset += dsize;
// reduce size by half for the next pass
mipmap_w >>= 1;
mipmap_h >>= 1;
if (mipmap_w <= 0) {
mipmap_w = 1;
}
if (mipmap_h <= 0) {
mipmap_h = 1;
}
}
if (texmem != nullptr) {
vm_free(texmem);
}
break;
}
} // end switch
tSlot->bitmap_handle = bitmap_handle;
// Remove the previous bytes of the slot before the new size replaces them. This function also
// runs for a texture that is filled again (a streaming animation, for example).
opengl_texture_release_bytes(tSlot);
tSlot->size = (dsize) ? ((doffset + dsize) - skip_size) : (tex_w * tex_h * byte_mult);
tSlot->w = (ushort)tex_w;
tSlot->h = (ushort)tex_h;
GL_texture_bytes_used += static_cast<size_t>(tSlot->size);
GL_textures_in_frame += tSlot->size;
GL_CHECK_FOR_ERRORS("end of create_texture_sub()");
return ret_val;
}
static GLenum opengl_get_internal_format(int handle, int bitmap_type, int bpp) {
// set the byte per pixel multiplier
auto byte_mult = (bpp >> 3);
// check for compressed image types
auto bm_handle = bm_is_compressed(handle);
switch (bm_handle) {
case DDS_DXT1:
case DDS_CUBEMAP_DXT1:
return GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
case DDS_DXT3:
case DDS_CUBEMAP_DXT3:
return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
break;
case DDS_DXT5:
case DDS_CUBEMAP_DXT5:
return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
case DDS_BC7:
return GL_COMPRESSED_RGBA_BPTC_UNORM_ARB;
case KTX_ETC2_RGB:
case KTX_ETC2_RGBA_EAC:
case KTX_ETC2_SRGB:
case KTX_ETC2_SRGBA_EAC:
case KTX_ETC2_RGB_A1:
case KTX_ETC2_SRGB_A1:
return ktx_map_ktx_format_to_gl_internal(bm_handle);
default:
// Not compressed
break;
}
if (byte_mult == 4) {
return (gr_screen.bits_per_pixel == 32) ? GL_RGBA8 : GL_RGB5_A1;
} else if (byte_mult == 3) {
return (gr_screen.bits_per_pixel == 32) ? GL_RGB8 : GL_RGB5;
} else if (byte_mult == 2) {
return GL_RGB5_A1;
} else if (byte_mult == 1) {
Assertion( bitmap_type == TCACHE_TYPE_AABITMAP, "Invalid type for bitmap: %s BMPMAN handle: %d. Type expected was 0, we got %d instead.\nThis can be caused by using texture compression on a non-power-of-2 texture.\n", bm_get_filename(handle), handle, bitmap_type );
return GL_R8;
} else {
return GL_RGBA8;
}
}
void opengl_determine_bpp_and_flags(int bitmap_handle, int bitmap_type, uint& bitmap_flags, int& bpp) {
bitmap_flags = 0;
bpp = 16;
switch (bitmap_type) {
case TCACHE_TYPE_AABITMAP:
bitmap_flags |= BMP_AABITMAP;
bpp = 8;
break;
case TCACHE_TYPE_CUBEMAP:
case TCACHE_TYPE_NORMAL:
bitmap_flags |= BMP_TEX_OTHER;
if (bm_get_type(bitmap_handle) == BM_TYPE_PCX) {
// PCX is special since the locking code only works with bpp = 16 for some reason
bpp = 16;
} else {
if (bm_has_alpha_channel(bitmap_handle)) {
bpp = 32; // Since this bitmap has an alpha channel we need to respect that
} else {
bpp = 24; // RGB, 8-bits per channel
}
}
break;
case TCACHE_TYPE_INTERFACE:
case TCACHE_TYPE_XPARENT:
bitmap_flags |= BMP_TEX_XPARENT;
if (bm_get_type(bitmap_handle) == BM_TYPE_PCX) {
// PCX is special since the locking code only works with bpp = 16 for some reason
bpp = 16;
} else {
bpp = 32; // RGBA, 8-bits per channel
}
break;
case TCACHE_TYPE_COMPRESSED:
switch ( bm_is_compressed(bitmap_handle) ) {
case DDS_DXT1: //dxt1
bpp = 24;
bitmap_flags |= BMP_TEX_DXT1;
break;
case DDS_DXT3: //dxt3
bpp = 32;
bitmap_flags |= BMP_TEX_DXT3;
break;
case DDS_DXT5: //dxt5
bpp = 32;
bitmap_flags |= BMP_TEX_DXT5;
break;
case DDS_BC7: //bc7
bpp = 32;
bitmap_flags |= BMP_TEX_BC7;
break;
case DDS_CUBEMAP_DXT1:
bpp = 24;
bitmap_flags |= BMP_TEX_CUBEMAP;
break;
case DDS_CUBEMAP_DXT3:
case DDS_CUBEMAP_DXT5:
bpp = 32;
bitmap_flags |= BMP_TEX_CUBEMAP;
break;
case KTX_ETC2_RGB:
case KTX_ETC2_SRGB:
bpp = 24;
bitmap_flags |= BMP_TEX_ETC2_RGB8;
break;
case KTX_ETC2_RGB_A1:
case KTX_ETC2_SRGB_A1:
bpp = 24;
bitmap_flags |= BMP_TEX_ETC2_RGBA1;
break;
case KTX_ETC2_RGBA_EAC:
case KTX_ETC2_SRGBA_EAC:
bpp = 32;
bitmap_flags |= BMP_TEX_ETC2_RGBA8;
break;
default:
Assert( 0 );
break;
}
break;
case TCACHE_TYPE_3DTEX:
bpp = 32;
break;
}
}
void opengl_tex_array_storage(GLenum target, GLint levels, GLenum format, GLint width, GLint height, GLint frames) {
if (target == GL_TEXTURE_CUBE_MAP) {
Assertion(frames == 1, "Cube map texture arrays aren't supported yet!");
if (GLAD_GL_ARB_texture_storage) {
// This version has a better way of specifying the texture storage
if ( levels == 1 ) {
// looks like we only have one mipmap for this cube map
// allocate additional storage for the mip maps we're going to later generate using glGenerateMipMap
levels = get_num_mipmap_levels(width, height);
}
glTexStorage2D(target, levels, format, width, height);
} else {
for (auto i = 0; i < 6; ++i) {
auto mip_width = width;
auto mip_height = height;
for (auto j = 0; j < levels; j++) {
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, j, format, mip_width, mip_height, 0, GL_RGBA, GL_UNSIGNED_BYTE,
nullptr);
mip_width = std::max(1, (mip_width / 2));
mip_height = std::max(1, (mip_height / 2));
}
}
// Make sure that the image is complete with the right number of mipmap levels
glTexParameteri(target, GL_TEXTURE_MAX_LEVEL, levels);
}
} else {
if (GLAD_GL_ARB_texture_storage) {
// This version has a better way of specifying the texture storage
glTexStorage3D(target, levels, format, width, height, frames);
} else {
for (auto i = 0; i < levels; i++) {
glTexImage3D(target, i, format, width, height, frames, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
width = std::max(1, (width / 2));
height = std::max(1, (height / 2));
}
// Make sure that the image is complete with the right number of mipmap levels
glTexParameteri(target, GL_TEXTURE_MAX_LEVEL, levels);
}
}
}
int opengl_create_texture(int bitmap_handle, int bitmap_type, tcache_slot_opengl *tslot)
{
GR_DEBUG_SCOPE("Create Texture");
if (tslot == NULL) {
return 0;
}
int num_frames = 0;
// This takes care of checking if the animation can be put into a texture array
auto animation_begin = bm_get_base_frame(bitmap_handle, &num_frames);
if (animation_begin < 0) {
// Some kind of error
return 0;
}
int width, height, depth = 1;
bm_get_info(animation_begin, &width, &height, nullptr, nullptr);
auto start_slot = bm_get_gr_info<tcache_slot_opengl>(animation_begin, true);
if (start_slot->bitmap_handle != -1) {
Assertion(start_slot->bitmap_handle != animation_begin, "opengl_create_texture was called for the same bitmap again!");
if (start_slot->texture_id != 0) {
// Delete the previous texture that was stored in this slot
GL_state.Texture.Delete(start_slot->texture_id);
glDeleteTextures(1, &start_slot->texture_id);
opengl_texture_release_bytes(start_slot);
start_slot->reset();
}
}
auto max_levels = bm_get_num_mipmaps(bitmap_handle);
auto base_level = 0;
auto resize = false;
// User bitmaps are excluded from culling because they are streaming surfaces (e.g. animations and
// video) that are updated through gr_update_texture() with the bitmap's own dimensions each frame,
// which requires the texture to match the bitmap's size.
if ( (Detail.hardware_textures < 4) && (bm_get_type(bitmap_handle) != BM_TYPE_USER)
&& (bitmap_type != TCACHE_TYPE_AABITMAP) && (bitmap_type != TCACHE_TYPE_INTERFACE)
&& (bitmap_type != TCACHE_TYPE_CUBEMAP) && (bitmap_type != TCACHE_TYPE_3DTEX)
&& ((bitmap_type != TCACHE_TYPE_COMPRESSED) || ((bitmap_type == TCACHE_TYPE_COMPRESSED) && (max_levels > 1))) )
{
if (max_levels == 1) {
// if we are going to cull the size then we need to force a resize
// Detail.hardware_textures goes from 0 to 4.
width /= (16 >> Detail.hardware_textures);
height /= (16 >> Detail.hardware_textures);
CLAMP(width, GL_min_texture_width, GL_max_texture_width);
CLAMP(height, GL_min_texture_height, GL_max_texture_height);
resize = true;
} else {
// we have mipmap levels so use those as a resize point (image should already be power-of-2)
base_level = -(Detail.hardware_textures - 4);
Assert(base_level >= 0);
if (base_level >= max_levels) {
base_level = max_levels - 1;
}
Assert( (max_levels - base_level) >= 1 );
// Adjust the size to match the size of the used base-mipmap
width >>= base_level;
height >>= base_level;
}
}
if ( (width < 1) || (height< 1) ) {
mprintf(("Bitmap %s is too small at %dx%d.\n", bm_get_filename(bitmap_handle), width, height));
return 0;
}
// there should only ever be one mipmap level for interface graphics!!!
if ( (bitmap_type == TCACHE_TYPE_INTERFACE) && (max_levels > 1) ) {
max_levels = 1;
}
// if we ended up locking a texture that wasn't originally compressed then this should catch it
if ( bitmap_type != TCACHE_TYPE_CUBEMAP && bitmap_type != TCACHE_TYPE_3DTEX && bm_is_compressed(bitmap_handle) ) {
bitmap_type = TCACHE_TYPE_COMPRESSED;
}
// Create the texture array and bind it
glGenTextures(1, &tslot->texture_id);
tslot->texture_target = GL_TEXTURE_2D_ARRAY;
if ( bitmap_type == TCACHE_TYPE_CUBEMAP ) {
tslot->texture_target = GL_TEXTURE_CUBE_MAP;
}
else if ( bitmap_type == TCACHE_TYPE_3DTEX ) {
tslot->texture_target = GL_TEXTURE_3D;
}
if (tslot->texture_id == 0) {
mprintf(("!!OpenGL DEBUG!! t->texture_id == 0\n"));
opengl_texture_release_bytes(tslot);
tslot->reset();
return 0;
}
// This is very important! All bitmaps are bound as texture arrays even if they only contain a single image. This
// simplifies shader handling since they only ever work with texture arrays instead of having to introduce another
// compile time flag for every single sampler
GL_state.Texture.SetTarget(tslot->texture_target);
GL_state.Texture.Enable(tslot->texture_id);
opengl_set_object_label(GL_TEXTURE, tslot->texture_id, bm_get_filename(bitmap_handle));
GLenum min_filter = GL_LINEAR;
auto mipmap_levels = max_levels - base_level;
// Needed in case we need to allocate more mipmaps than are in the texture file
auto allocated_mipmap_levels = mipmap_levels;
if (mipmap_levels > 1) {
min_filter = (GL_mipmap_filter) ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR_MIPMAP_NEAREST;
if ( GLAD_GL_EXT_texture_filter_anisotropic ) {
glTexParameterf(tslot->texture_target, GL_TEXTURE_MAX_ANISOTROPY_EXT, GL_anisotropy);
}
} else if (mipmap_levels == 1 && bitmap_type == TCACHE_TYPE_CUBEMAP) {
Assertion(num_frames == 1, "Cube map arrays are not supported yet!");
// For cubemaps without mipmaps, we will generate them later so we need to make sure that enough space is
// allocated for the generated mipmaps
allocated_mipmap_levels = get_num_mipmap_levels(width, height);
min_filter = GL_LINEAR_MIPMAP_LINEAR;
}
else if (mipmap_levels == 1 && bitmap_type == TCACHE_TYPE_3DTEX) {
//3D-Tex just filters linearly always
min_filter = GL_LINEAR;
bm_get_depth(bitmap_handle, &depth);
}
glTexParameteri(tslot->texture_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(tslot->texture_target, GL_TEXTURE_MIN_FILTER, min_filter);
glTexParameteri(tslot->texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(tslot->texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(tslot->texture_target, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
tslot->wrap_mode = GL_CLAMP_TO_EDGE;
uint bitmap_flags;
int bits_per_pixel;
opengl_determine_bpp_and_flags(animation_begin, bitmap_type, bitmap_flags, bits_per_pixel);
auto intFormat = opengl_get_internal_format(bitmap_handle, bitmap_type, bits_per_pixel);
opengl_tex_array_storage(tslot->texture_target, allocated_mipmap_levels, intFormat, width, height, bitmap_type == TCACHE_TYPE_3DTEX ? depth : num_frames);
bool frames_loaded = true;
for (int frame = animation_begin; frame < animation_begin + num_frames; ++frame) {
#ifndef NDEBUG
// I'm not sure if these values are consistent across the whole animation but they really should be.
// This should catch any instances where this assumption isn't right
uint debug_flags = 0;
int debug_bpp;
opengl_determine_bpp_and_flags(frame, bitmap_type, debug_flags, debug_bpp);
Assertion(debug_flags == bitmap_flags, "Bitmap flags mismatch detected! Get a coder to take a look at this bitmap: %s.", bm_get_filename(frame));
Assertion(debug_bpp == bits_per_pixel, "Bits per pixel mismatch detected! Get a coder to take a look at this bitmap: %s.", bm_get_filename(frame));
#endif
// lock the bitmap into the proper format
auto bmp = bm_lock(frame, bits_per_pixel, bitmap_flags);
if ( bmp == NULL ) {
mprintf(("Couldn't lock bitmap %d (%s).\n", frame, bm_get_filename(frame) ));
continue;
}
auto index = frame - animation_begin;
auto frame_slot = bm_get_gr_info<tcache_slot_opengl>(frame, true);
// The copy below overwrites the size of the slot, thus the old bytes must go away first
opengl_texture_release_bytes(frame_slot);
*frame_slot = *tslot; // Copy the existing properties to the slot of this texture
// The copy also brought in the size of tslot, which belongs to a different slot and is
// already part of GL_texture_bytes_used. opengl_texture_set_level() below gives this slot
// its own size.
frame_slot->size = 0;
frame_slot->array_index = (uint32_t) index;
// set the bits per pixel
frame_slot->bpp = bmp->bpp;
// max number of mipmap levels (NOTE: this is the max number used by the API, not how many true mipmap levels there are in the image!!)
frame_slot->mipmap_levels = mipmap_levels;
frame_slot->used = true; // Mark all frames as used
frame_slot->array_index = (uint32_t) (frame - animation_begin);
// call the helper
int ret_val = opengl_texture_set_level(frame, bitmap_type, bmp->w, bmp->h, width, height, depth, (ubyte*)bmp->data,
frame_slot, base_level, mipmap_levels, resize, intFormat);
frames_loaded = frames_loaded && ret_val;
// unlock the bitmap
bm_unlock(frame);
}
if ( tslot->mipmap_levels == 1 && bitmap_type == TCACHE_TYPE_CUBEMAP ) {
Assertion(num_frames == 1, "Cube map arrays are not supported yet!");
// generate mip maps for cube maps so we can get glossy reflections; necessary for gloss maps and
// physically-based lighting OGL_EXT_FRAMEBUFFER_OBJECT required to use glGenerateMipmap()
glGenerateMipmap(tslot->texture_target);
}
// if we successfully sent the texture to API memory then go ahead and dump
// the rest of the bitmap data in system memory.
// NOTE: this doesn't do anything for user bitmaps (like screen grabs or streamed animations)
if (frames_loaded) {
bm_unload_fast(bitmap_handle);
}
return frames_loaded;
}
// WARNING: Needs to match what is in bm_internal.h!!!!!
#define RENDER_TARGET_DYNAMIC 17
int gr_opengl_tcache_set_internal(int bitmap_handle, int bitmap_type, float *u_scale, float *v_scale, uint32_t *array_index, int tex_unit = 0)
{
int ret_val = 1;
GR_DEBUG_SCOPE("Activate texture");
if (GL_last_detail != Detail.hardware_textures) {
GL_last_detail = Detail.hardware_textures;
opengl_tcache_flush();
}
auto t = bm_get_gr_info<tcache_slot_opengl>(bitmap_handle, true);
if (!bm_is_render_target(bitmap_handle) && t->bitmap_handle < 0)
{
GL_state.Texture.SetActiveUnit(tex_unit);
ret_val = opengl_create_texture( bitmap_handle, bitmap_type, t );
}
// everything went ok
if (ret_val && t->texture_id) {
*u_scale = t->u_scale;
*v_scale = t->v_scale;
*array_index = t->array_index;
GL_state.Texture.Enable(tex_unit, t->texture_target, t->texture_id);
if ( (t->wrap_mode != GL_texture_addressing) && (bitmap_type != TCACHE_TYPE_AABITMAP)
&& (bitmap_type != TCACHE_TYPE_INTERFACE) && (bitmap_type != TCACHE_TYPE_CUBEMAP))
{
// In this case we need to make sure that the texture unit is actually active
GL_state.Texture.SetActiveUnit(tex_unit);
glTexParameteri(t->texture_target, GL_TEXTURE_WRAP_S, GL_texture_addressing);
glTexParameteri(t->texture_target, GL_TEXTURE_WRAP_T, GL_texture_addressing);
glTexParameteri(t->texture_target, GL_TEXTURE_WRAP_R, GL_texture_addressing);
t->wrap_mode = GL_texture_addressing;
}
}
// gah
else {
mprintf(("Texturing disabled for bitmap %d (%s) due to internal error.\n", bitmap_handle, bm_get_filename(bitmap_handle)));
return 0;
}
return 1;
}
int gr_opengl_tcache_set(int bitmap_handle, int bitmap_type, float *u_scale, float *v_scale, uint32_t *array_index, int stage)
{
Assertion(u_scale != nullptr, "U scale must be a valid pointer!");
Assertion(v_scale != nullptr, "V scale must be a valid pointer!");
Assertion(array_index != nullptr, "Array index must be a valid pointer!");
int rc = 0;
if (bitmap_handle < 0) {
return 0;
}
GL_CHECK_FOR_ERRORS("start of tcache_set()");
// set special type if it's so, needed to be right later, but cubemaps are special
if ( !(bitmap_type == TCACHE_TYPE_CUBEMAP) && !(bitmap_type == TCACHE_TYPE_3DTEX) ) {
int type = bm_get_tcache_type(bitmap_handle);
if (type != TCACHE_TYPE_NORMAL) {
bitmap_type = type;
}
}
rc = gr_opengl_tcache_set_internal(bitmap_handle, bitmap_type, u_scale, v_scale, array_index, stage);
GL_CHECK_FOR_ERRORS("end of tcache_set()");
return rc;
}
void opengl_preload_init()
{
if (gr_screen.mode != GraphicsAPI::OpenGL)
return;
// opengl_tcache_flush ();
}
int gr_opengl_preload(int bitmap_num, int is_aabitmap)
{
float u_scale, v_scale;
int retval;
Assert( gr_screen.mode == GraphicsAPI::OpenGL );
if ( !GL_should_preload ) {
return 0;
}
uint32_t array_index;
retval = gr_opengl_tcache_set(bitmap_num, (is_aabitmap) ? TCACHE_TYPE_AABITMAP : TCACHE_TYPE_NORMAL, &u_scale, &v_scale, &array_index);
if ( !retval ) {
mprintf(("Texture upload failed!\n"));
}
return retval;
}
void gr_opengl_set_texture_addressing(int mode)
{
GL_CHECK_FOR_ERRORS("start of set_texture_addressing()");
switch (mode) {
case TMAP_ADDRESS_CLAMP:
GL_texture_addressing = GL_CLAMP_TO_EDGE;
break;
case TMAP_ADDRESS_WRAP:
GL_texture_addressing = GL_REPEAT;
break;
case TMAP_ADDRESS_MIRROR: {
GL_texture_addressing = GL_MIRRORED_REPEAT;
break;
}
default:
Int3();
break;
}
GL_CHECK_FOR_ERRORS("end of set_texture_addressing()");
}
int opengl_compress_image( ubyte **compressed_data, ubyte *in_data, int width, int height, int alpha, int num_mipmaps )
{
Assert( in_data != NULL );
if ( !GLAD_GL_EXT_texture_compression_s3tc ) {
return 0;
}
GL_CHECK_FOR_ERRORS("start of compress_image()");
GLuint tex;
GLint compressed = GL_FALSE;
GLint compressed_size = 0;
ubyte *out_data = NULL;
GLint testing = 0;
GLint intFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
GLenum texFormat = GL_UNSIGNED_BYTE;
GLenum glFormat = GL_BGR;
int i;
if (alpha) {
intFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
texFormat = GL_UNSIGNED_INT_8_8_8_8_REV;
glFormat = GL_BGRA;
}
glGenTextures(1, &tex);
GL_state.Texture.SetActiveUnit(0);
GL_state.Texture.SetTarget(GL_TEXTURE_2D);
GL_state.Texture.Enable(tex);
// a quick proxy test. this will tell us if it's possible without wasting a lot of time and resources in the attempt
glTexImage2D(GL_PROXY_TEXTURE_2D, 0, intFormat, width, height, 0, glFormat, texFormat, in_data);
glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_COMPRESSED, &compressed);
glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &testing);
if (compressed == GL_TRUE) {
glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &compressed);
}
if ( (compressed == GL_FALSE) || (compressed != intFormat) || (testing == 0) ) {
GL_state.Texture.Delete(tex);
glDeleteTextures(1, &tex);
return 0;
}
#ifndef USE_OPENGL_ES
// use best compression quality
glHint(GL_TEXTURE_COMPRESSION_HINT, GL_NICEST);
#endif
// alright, it should work if we are still here, now do it for real
glTexImage2D(GL_TEXTURE_2D, 0, intFormat, width, height, 0, glFormat, texFormat, in_data);
// if we got this far then it should have worked, but check anyway
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_COMPRESSED, &compressed);
Assert( compressed != GL_FALSE );
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &compressed);
Assert( compressed == intFormat );
// for each mipmap level we generate go ahead and figure up the total memory required
for (i = 0; i < num_mipmaps; i++) {
glGetTexLevelParameteriv(GL_TEXTURE_2D, i, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &testing);
compressed_size += testing;
}
out_data = (ubyte*)vm_malloc(compressed_size * sizeof(ubyte));
Assert( out_data != NULL );
memset(out_data, 0, compressed_size * sizeof(ubyte));
// reset compressed_size and go back through each mipmap level to get both size and the image data itself
compressed_size = 0;
for (i = 0; i < num_mipmaps; i++) {
glGetTexLevelParameteriv(GL_TEXTURE_2D, i, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &testing);
glGetCompressedTexImage(GL_TEXTURE_2D, i, out_data + compressed_size);
compressed_size += testing;
}
#ifndef USE_OPENGL_ES
glHint(GL_TEXTURE_COMPRESSION_HINT, GL_DONT_CARE);
#endif
GL_state.Texture.Delete(tex);
glDeleteTextures(1, &tex);
// send the data back out
*compressed_data = out_data;
GL_CHECK_FOR_ERRORS("end of compress_image()");
return compressed_size;
}
int opengl_get_texture( GLenum target, GLenum pixel_format, GLenum data_format, int num_mipmaps, int width, int height, int bytes_per_pixel, void* image_data, int offset )
{
Assertion(target != GL_TEXTURE_2D_ARRAY, "This code does not support texture arrays bitmaps!");
int m_offset = offset;
int m_width = width;
int m_height = height;
for ( int i = 0; i < num_mipmaps; i++ ) {
glGetTexImage(target, i, pixel_format, data_format, (ubyte*)image_data + m_offset);
m_offset += (m_width * m_height * bytes_per_pixel);
// reduce by half for next mipmap level
m_width >>= 1;
m_height >>= 1;
if (m_width < 1)
m_width = 1;
if (m_height < 1)
m_height = 1;
}
return m_offset;
}
ubyte* gr_opengl_get_bitmap_from_texture(int bitmap_num, int* width_out, int* height_out)
{
*width_out = 0;
*height_out = 0;
float u,v;
uint32_t array_index = 0;
if ( !gr_opengl_tcache_set(bitmap_num, TCACHE_TYPE_NORMAL, &u, &v, &array_index) ) {
return nullptr;
}
auto *ts = bm_get_gr_info<tcache_slot_opengl>(bitmap_num, true);
GLenum pixel_format = GL_RGB;
GLenum data_format = GL_UNSIGNED_BYTE;
int bytes_per_pixel = 3 * sizeof(ubyte);
if ( bm_has_alpha_channel(bitmap_num) ) {
pixel_format = GL_RGBA;
bytes_per_pixel = 4 * sizeof(ubyte);
}
Assertion(ts->texture_target == GL_TEXTURE_2D_ARRAY, "Unexpected texture target encountered!");
// The texture in graphics memory may not match the dimensions or frame count that bmpman reports for
// this handle, e.g. when mipmap levels are culled at lower texture detail settings, so size the
// readback from what OpenGL will actually write, not from the bitmap or the texture cache slot.
GLint gl_width = 0, gl_height = 0, gl_layers = 0;
glGetTexLevelParameteriv(ts->texture_target, 0, GL_TEXTURE_WIDTH, &gl_width);
glGetTexLevelParameteriv(ts->texture_target, 0, GL_TEXTURE_HEIGHT, &gl_height);
glGetTexLevelParameteriv(ts->texture_target, 0, GL_TEXTURE_DEPTH, &gl_layers);
if ( (gl_width < 1) || (gl_height < 1) || (gl_layers < 1) || (array_index >= static_cast<uint32_t>(gl_layers)) ) {
mprintf(("Cannot read bitmap %d (%s) back from its texture; OpenGL reports level 0 as %dx%d with %d layer(s) but layer %u is needed.\n",
bitmap_num, bm_get_filename(bitmap_num), gl_width, gl_height, gl_layers, array_index));
return nullptr;
}
// At maximum texture detail no culling occurs on upload, so the texture must match the dimensions
// recorded in the texture cache slot; if this trips, we have another slot mismatch.
Assertion((Detail.hardware_textures < 4) || ((gl_width == ts->w) && (gl_height == ts->h)),
"Texture readback size mismatch for bitmap %d (%s): OpenGL reports level 0 as %dx%d but the texture cache slot expects %dx%d.",
bitmap_num, bm_get_filename(bitmap_num), gl_width, gl_height, ts->w, ts->h);
// The size of a single layer in the array
size_t slice_size = static_cast<size_t>(gl_width) * gl_height * bytes_per_pixel;
// We can't read a specific layer of the texture so we need to read the entire texture and then memcpy the right part from that...
// Some drivers write tiny compressed textures back in whole 4x4 block granularity, so pad each layer
// of the staging buffer to full blocks to keep any such overwrite within bounds.
size_t padded_slice_size = static_cast<size_t>((gl_width + 3) & ~3) * ((gl_height + 3) & ~3) * bytes_per_pixel;
std::unique_ptr<std::uint8_t[]> buffer(new std::uint8_t[padded_slice_size * gl_layers]);
// Copy the entire texture level into the bitmap
glGetTexImage(ts->texture_target, 0, pixel_format, data_format, buffer.get());
auto data_out = reinterpret_cast<ubyte*>(vm_malloc(slice_size));
memcpy(data_out, buffer.get() + array_index * slice_size, slice_size);
*width_out = gl_width;
*height_out = gl_height;
return data_out;
}
void gr_opengl_get_texture_scale(int bitmap_handle, float *u_scale, float *v_scale)
{
auto t = bm_get_gr_info<tcache_slot_opengl>(bitmap_handle, true);
*u_scale = t->u_scale;
*v_scale = t->v_scale;
}
/**
* Sends a texture object out to "image_data"
*
* Image_data should be memory which is already allocated. Function should only
* be used for uncompressed 24-bit or 32-bit (distiguished by "alpha" var) images
*
* @return 0 on failure
* @return size of data on success
*/
size_t opengl_export_render_target( int slot, int width, int height, int alpha, int num_mipmaps, ubyte *image_data )
{
auto ts = bm_get_gr_info<tcache_slot_opengl>(slot);
GL_CHECK_FOR_ERRORS("start of export_image()");
if (!image_data) {
mprintf(("OpenGL ERROR: Tried to export a texture without a valid export location!\n"));
return 0;
}
if (!ts->texture_target) {
mprintf(("OpenGL ERROR: Tried to export a texture for which I don't know the texture target!\n"));
return 0;
}
if (ts->mipmap_levels != num_mipmaps) {
mprintf(("OpenGL ERROR: Number of mipmap levels requested is different from number available!\n"));
return 0;
}
if ( (ts->texture_target != GL_TEXTURE_2D) && (ts->texture_target != GL_TEXTURE_CUBE_MAP) ) {
mprintf(("OpenGL ERROR: Only 2D textures and cube maps can be exported!\n"));
return 0;
}
if ( (ts->w != width) || (ts->h != height) ) {
mprintf(("OpenGL ERROR: Passed width and height do not match values for texture!\n"));
return 0;
}
int faces = 1;
int m_width = width;
int m_height = height;
int m_bpp = (alpha) ? 4 : 3;
int m_offset = 0;
GLenum target = (ts->texture_target == GL_TEXTURE_CUBE_MAP) ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : GL_TEXTURE_2D;
if (ts->texture_target == GL_TEXTURE_CUBE_MAP)
faces = 6;
GL_state.Texture.SetActiveUnit(0);
GL_state.Texture.SetTarget(ts->texture_target);
GL_state.Texture.Enable(ts->texture_id);
for (int i = 0; i < faces; i++) {
m_offset = opengl_get_texture(
target + i,
(alpha) ? GL_BGRA : GL_BGR,
(alpha) ? GL_UNSIGNED_INT_8_8_8_8_REV : GL_UNSIGNED_BYTE,
ts->mipmap_levels,
m_width,
m_height,
m_bpp,
image_data,
m_offset
);
}
GL_CHECK_FOR_ERRORS("end of export_image()");
return (size_t)m_offset;
}
void gr_opengl_update_texture(int bitmap_handle, int bpp, const ubyte* data, int width, int height)
{
GLenum texFormat, glFormat;
auto t = bm_get_gr_info<tcache_slot_opengl>(bitmap_handle);
if(!t->texture_id)
return;
// This overwrites the texture with data at the bitmap's own size, so the texture must not have been
// culled when it was created (user bitmaps are exempt from texture detail culling for this reason).
Assertion((t->w == width) && (t->h == height),
"gr_opengl_update_texture() called for bitmap %s with %dx%d data, but its texture is %dx%d.",
bm_get_filename(bitmap_handle), width, height, t->w, t->h);
int byte_mult = (bpp >> 3);
int true_byte_mult = (t->bpp >> 3);
ubyte* texmem = NULL;
// GL_BGRA_EXT is *much* faster with some hardware/drivers
if (true_byte_mult == 4) {
texFormat = GL_UNSIGNED_INT_8_8_8_8_REV;
glFormat = GL_BGRA;
} else if (true_byte_mult == 3) {
texFormat = GL_UNSIGNED_BYTE;
glFormat = GL_BGR;
} else {
texFormat = GL_UNSIGNED_SHORT_1_5_5_5_REV;
glFormat = GL_BGRA;
}
if (byte_mult == 1) {
texFormat = GL_UNSIGNED_BYTE;
glFormat = GL_RED;
texmem = (ubyte *) vm_malloc (width*height*byte_mult);
ubyte* texmemp = texmem;
Assert( texmem != NULL );
int luminance = 0;
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
if ( (i < height) && (j < width) ) {
if ( true_byte_mult > 1 ) {
luminance = 0;
if ( true_byte_mult > 3 ) {
for (int k = 0; k < 3; k++) {
luminance += data[(i*width+j)*true_byte_mult+k];
}
*texmemp++ = (ubyte)((luminance / 3) * (data[(i*width+j)*true_byte_mult+3]/255.0f));
} else {
for (int k = 0; k < true_byte_mult; k++) {
luminance += data[(i*width+j)*true_byte_mult+k];
}
*texmemp++ = (ubyte)(luminance / true_byte_mult);
}
} else {
*texmemp++ = data[i*width+j];
}
} else {
*texmemp++ = 0;
}
}
}
}
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
GL_state.Texture.SetActiveUnit(0);
GL_state.Texture.SetTarget(t->texture_target);
GL_state.Texture.Enable(t->texture_id);
glTexSubImage3D(t->texture_target, 0, 0, 0, t->array_index, width, height, 1, glFormat, texFormat, (texmem)?texmem:data);
if (texmem != NULL)
vm_free(texmem);
}
// -----------------------------------------------------------------------------
// GL_EXT_framebuffer_object stuff (ie, render-to-texture)
//
struct fbo_t {
// these first vars should only be modified in opengl_make_render_target()
GLuint renderbuffer_id = 0;
GLuint framebuffer_id = 0;
size_t renderbuffer_bytes = 0; // for the profiler overlay's memory panel; see GL_renderbuffer_bytes_used
int width = 0;
int height = 0;
// these next 2 should only be modifed in opengl_set_render_target()
int working_handle = -1;
int is_static = 0;
int fbo_id = -1;
};
static SCP_vector<fbo_t> RenderTarget;
static fbo_t *render_target = NULL;
static int next_fbo_id = 0;
// Running total of bytes used by depth/stencil renderbuffers across all live FBOs, for the
// profiler overlay's memory panel. Kept in sync with each fbo_t's renderbuffer_bytes at every
// create/delete site below rather than summed on demand, since RenderTarget entries are reused
// (opengl_get_free_fbo) and reset in place.
static size_t GL_renderbuffer_bytes_used = 0;
static fbo_t* opengl_get_fbo(int id) {
if (id < 0) {
return nullptr;
}
for (auto& target : RenderTarget) {
if (target.fbo_id == id) {
return &target;
}
}
return nullptr;
}
static fbo_t* opengl_get_free_fbo() {
fbo_t* fbo = nullptr;
for (auto& target : RenderTarget) {
if (target.fbo_id < 0) {
// This slot is free
fbo = &target;
}
}
if (fbo == nullptr) {
RenderTarget.push_back(fbo_t());
fbo = &RenderTarget.back();
}
fbo->fbo_id = next_fbo_id;
++next_fbo_id;
return fbo;
}
static void opengl_free_fbo_slot(int id) {
auto fbo = opengl_get_fbo(id);
Assertion(fbo != nullptr, "Invalid id passed to opengl_free_fbo_slot!");
// Reset this slot using the default constructor
*fbo = fbo_t();
}
int opengl_check_framebuffer()
{
GLenum status;
status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE) {
char err_txt[100] = { 0 };
switch (status) {
case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
strcpy_s(err_txt, "Incomplete attachments!");
break;
case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
strcpy_s(err_txt, "Missing one or more image attachments!");
break;
#ifndef USE_OPENGL_ES
case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
strcpy_s(err_txt, "Draw buffer attachment point is NONE!");
break;
case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
strcpy_s(err_txt, "Read buffer attachment point is NONE!");
break;
#else
case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
strcpy_s(err_txt, "Inconsistent multisample state (samples/resolve mismatch)!");
break;
#endif
case GL_FRAMEBUFFER_UNSUPPORTED:
strcpy_s(err_txt, "Attached images violate current FBO restrictions!");
break;
default:
strcpy_s(err_txt, "Unknown error!\n");
break;
}
mprintf(("Framebuffer ERROR: %s\n", err_txt));
return (int)status;
}
return 0;
}
void opengl_kill_render_target(bitmap_slot* slot)
{
// this will happen when opengl_kill_all_render_targets() gets called first on exit
if ( RenderTarget.empty() ) {
return;
}
auto ts = static_cast<tcache_slot_opengl*>(slot->gr_info);
auto fbo = opengl_get_fbo(ts->fbo_id);
// this may happen when textures are flushed, the w and h will get reset to 0
if ( fbo == nullptr ) {
return;
}
if (fbo->framebuffer_id) {
glDeleteFramebuffers(1, &fbo->framebuffer_id);
fbo->framebuffer_id = 0;
}
if (fbo->renderbuffer_id) {
glDeleteRenderbuffers(1, &fbo->renderbuffer_id);
fbo->renderbuffer_id = 0;
GL_renderbuffer_bytes_used -= fbo->renderbuffer_bytes;
fbo->renderbuffer_bytes = 0;
}
opengl_free_fbo_slot(fbo->fbo_id);
}
void gr_opengl_get_memory_stats(gr_memory_stats& stats)
{
stats.gpu_purpose_valid = true;
// The textures of the bitmap cache. Render targets that the game creates through bmpman are
// part of this group, which matches the Vulkan backend: its bm_make_render_target() images
// also go into the texture group.
stats.gpu_texture_bytes = GL_texture_bytes_used;
stats.gpu_geometry_bytes = opengl_get_geometry_bytes();
// The render target group holds the resources of the backend itself: the depth/stencil
// renderbuffers of the bitmap render targets, the scene and backbuffer textures, the
// post-processing textures, and the shadow map.
stats.gpu_render_target_bytes = GL_renderbuffer_bytes_used + opengl_get_scene_render_target_bytes() +
opengl_get_postprocessing_render_target_bytes() + opengl_get_shadow_map_bytes();
}
void opengl_kill_all_render_targets()
{
for (size_t i = 0; i < RenderTarget.size(); i++) {
fbo_t *fbo = &RenderTarget[i];
if (fbo->framebuffer_id) {
glDeleteFramebuffers(1, &fbo->framebuffer_id);
fbo->framebuffer_id = 0;
}
if (fbo->renderbuffer_id) {
glDeleteRenderbuffers(1, &fbo->renderbuffer_id);
fbo->renderbuffer_id = 0;
GL_renderbuffer_bytes_used -= fbo->renderbuffer_bytes;
fbo->renderbuffer_bytes = 0;
}
}
RenderTarget.clear();
// Every renderbuffer is gone now, thus the total must go to 0. A value that is not 0 shows
// that a site which deletes a renderbuffer does not correct GL_renderbuffer_bytes_used.
Assertion(GL_renderbuffer_bytes_used == 0,
"OpenGL renderbuffer memory tracking lost " SIZE_T_ARG " bytes. A renderbuffer delete site is missing.",
GL_renderbuffer_bytes_used);
}
int opengl_set_render_target( int slot, int face, int is_static )
{
tcache_slot_opengl *ts = NULL;
fbo_t *fbo = NULL;
GL_CHECK_FOR_ERRORS("start of set_render_target()");
if (slot < 0) {
if ( (render_target != nullptr) && (render_target->working_handle >= 0) ) {
if (bm_get_gr_info<tcache_slot_opengl>(render_target->working_handle)->mipmap_levels > 1) {
gr_opengl_bm_generate_mip_maps(render_target->working_handle);
}
if (render_target->is_static && Cmdline_save_render_targets) {
extern void gr_opengl_bm_save_render_target(int slot);
gr_opengl_bm_save_render_target(render_target->working_handle);
}
}
if(Cmdline_window_res) {
GL_state.BindFrameBuffer(Back_framebuffer);
}
else {
GL_state.BindFrameBuffer(0);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
}
// done with this render target so lets move on
render_target = NULL;
GL_rendering_to_texture = false;
GL_CHECK_FOR_ERRORS("end of set_render_target(0)");
return 1;
}
ts = bm_get_gr_info<tcache_slot_opengl>(slot);
Assert( ts != NULL );
if (!ts->texture_id) {
Int3();
return 0;
}
fbo = opengl_get_fbo(ts->fbo_id);
if (fbo == NULL) {
mprintf(("Tried to get an OpenGL FBO that didn't exist!\n"));
return 0;
}
// Since framebuffer_id is only ever 0 or assigned by glGenFramebuffer, it must be valid if not 0
// This check previously queried !glIsFramebuffer(fbo->framebuffer_id) for the same effect but at significantly higher performance costs.
if ( fbo->framebuffer_id == 0 ) {
Int3();
return 0;
}
// glBindRenderbuffer(GL_RENDERBUFFER, fbo->renderbuffer_id);
GL_state.BindFrameBuffer(fbo->framebuffer_id);
if (ts->texture_target == GL_TEXTURE_CUBE_MAP) {
// For cubemaps we can enable one of the six faces for rendering
Assert( (face >= 0) && (face < 6) );
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, ts->texture_id, 0);
} else {
// Check if the face is valid for this case
Assert( face <= 0 );
}
// glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, fbo->renderbuffer_id);
fbo->working_handle = slot;
fbo->is_static = is_static;
// save current fbo for later use
render_target = fbo;
GL_rendering_to_texture = true;
GL_CHECK_FOR_ERRORS("end of set_render_target()");
return 1;
}
int opengl_make_render_target( int handle, int *w, int *h, int *bpp, int *mm_lvl, int flags )
{
GR_DEBUG_SCOPE("Make OpenGL render target");
Assert( !GL_rendering_to_texture );
// got to have at least width and height!
if (!w || !h) {
Int3();
return 0;
}
// size check
if (*w > GL_max_renderbuffer_size) {
*w = GL_max_renderbuffer_size;
}
if (*h > GL_max_renderbuffer_size) {
*h = GL_max_renderbuffer_size;
}
auto ts = bm_get_gr_info<tcache_slot_opengl>(handle);
// now on to the good parts...
fbo_t* new_fbo = opengl_get_free_fbo();
ts->fbo_id = new_fbo->fbo_id;
if (flags & BMP_FLAG_CUBEMAP) {
opengl_set_texture_target( GL_TEXTURE_CUBE_MAP );
} else {
opengl_set_texture_target( GL_TEXTURE_2D_ARRAY );
}
// initialize color texture
glGenTextures(1, &ts->texture_id);
GL_state.Texture.SetActiveUnit(0);
GL_state.Texture.SetTarget(GL_texture_target);
GL_state.Texture.Enable(ts->texture_id);
GLint min_filter = GL_LINEAR;
if (flags & BMP_FLAG_RENDER_TARGET_MIPMAP) {
min_filter = GL_LINEAR_MIPMAP_LINEAR;
}
glTexParameteri(GL_texture_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_texture_target, GL_TEXTURE_MIN_FILTER, min_filter);
glTexParameteri(GL_texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
if (flags & BMP_FLAG_RENDER_TARGET_MIPMAP) {
extern int get_num_mipmap_levels(int w, int h);
ts->mipmap_levels = get_num_mipmap_levels(*w, *h);
} else {
ts->mipmap_levels = 1;
}
// Initialize the texture storage of the framebuffer. Framebuffers always have only a single layer in the texture array
// This automatically handles cubemaps correctly
opengl_tex_array_storage(GL_state.Texture.GetTarget(), ts->mipmap_levels, GL_RGBA8, *w, *h, 1);
if (flags & BMP_FLAG_RENDER_TARGET_MIPMAP) {
glGenerateMipmap(GL_state.Texture.GetTarget());
}
glTexParameteri(GL_texture_target, GL_TEXTURE_MAX_LEVEL, ts->mipmap_levels - 1);
// Record the bytes of the color texture, which opengl_tex_array_storage() gave the format
// GL_RGBA8 (4 bytes for each pixel). A cube map holds 6 faces of that size.
size_t rt_texture_bytes = 0;
for (int level = 0; level < ts->mipmap_levels; ++level) {
auto level_w = static_cast<size_t>(std::max(1, *w >> level));
auto level_h = static_cast<size_t>(std::max(1, *h >> level));
rt_texture_bytes += level_w * level_h * 4;
}
if (flags & BMP_FLAG_CUBEMAP) {
rt_texture_bytes *= 6;
}
// A slot that held a render target before must lose its old bytes first.
opengl_texture_release_bytes(ts);
// The size field of the slot is an int, thus the value must stay inside its range. The counter
// takes the same (clamped) value, which keeps the add here and the subtract in
// opengl_texture_release_bytes() equal.
ts->size = static_cast<int>(std::min(rt_texture_bytes, static_cast<size_t>(INT_MAX)));
GL_texture_bytes_used += static_cast<size_t>(ts->size);
GL_state.Texture.Enable(0);
// render buffer (depth)
if (flags & BMP_FLAG_RENDER_TARGET_DEPTH_ATTACHMENT) {
glGenRenderbuffers(1, &new_fbo->renderbuffer_id);
glBindRenderbuffer(GL_RENDERBUFFER, new_fbo->renderbuffer_id);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, *w, *h);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
// GL_DEPTH24_STENCIL8 is 4 bytes/pixel
new_fbo->renderbuffer_bytes = static_cast<size_t>(*w) * static_cast<size_t>(*h) * 4;
GL_renderbuffer_bytes_used += new_fbo->renderbuffer_bytes;
}
// frame buffer
glGenFramebuffers(1, &new_fbo->framebuffer_id);
GL_state.BindFrameBuffer(new_fbo->framebuffer_id);
if (flags & BMP_FLAG_CUBEMAP) {
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X, ts->texture_id, 0);
} else {
// Since we use texture arrays for all bitmaps we use a single image array here
glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, ts->texture_id, 0, 0);
}
if (flags & BMP_FLAG_RENDER_TARGET_DEPTH_ATTACHMENT) {
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, new_fbo->renderbuffer_id);
}
if ( opengl_check_framebuffer() ) {
// Oops!! reset everything and then bail
mprintf(("OpenGL: Unable to create FBO!\n"));
auto fbo_id = ts->fbo_id;
GL_state.BindFrameBuffer(0);
GL_state.Texture.Delete(ts->texture_id);
glDeleteTextures(1, &ts->texture_id);
opengl_texture_release_bytes(ts);
ts->reset();
glDeleteFramebuffers(1, &new_fbo->framebuffer_id);
if (new_fbo->renderbuffer_id) {
glDeleteRenderbuffers(1, &new_fbo->renderbuffer_id);
new_fbo->renderbuffer_id = 0;
GL_renderbuffer_bytes_used -= new_fbo->renderbuffer_bytes;
new_fbo->renderbuffer_bytes = 0;
}
opengl_set_texture_target();
opengl_free_fbo_slot(fbo_id);
return 0;
}
// save anything that needs saving, cleanup, and then exit
ts->w = (ushort)*w;
ts->h = (ushort)*h;
ts->bpp = 24;
ts->texture_target = GL_state.Texture.GetTarget();
ts->bitmap_handle = -1;//handle;
ts->u_scale = 1.0f;
ts->v_scale = 1.0f;
ts->used = true;
ts->array_index = 0;
new_fbo->width = ts->w;
new_fbo->height = ts->h;
if (bpp) {
*bpp = ts->bpp;
}
if (mm_lvl) {
*mm_lvl = ts->mipmap_levels;
}
// Clear the new Texture to black with alpha 1.0
glClearColor(0.0f,0.0f,0.0f,1.0f);
glClear(GL_COLOR_BUFFER_BIT);
GL_state.BindFrameBuffer(0);
opengl_set_texture_target();
nprintf(("OpenGL Debug","OpenGL: Created %ix%i FBO!\n", ts->w, ts->h));
return 1;
}
/**
* @fn GLuint opengl_get_rtt_framebuffer()
*
* @brief Gets the current RTT framebuffer.
*
* Gets the OpenGL framebuffer ID of the currently in use RTT framebuffer.
* If there is currently none such framebuffer in use then this function returns
* 0 so it can be used in any place where the framebuffer should be reset to the
* default drawing surface.
*
* @author m!m
* @date 14.12.2011
*
* @return The current RTT FBO ID or 0 when not doing RTT.
*/
GLuint opengl_get_rtt_framebuffer()
{
if (render_target == nullptr || render_target->working_handle < 0)
return 0;
else
return render_target->framebuffer_id;
}
void gr_opengl_bm_generate_mip_maps(int handle)
{
tcache_slot_opengl *ts = NULL;
ts = bm_get_gr_info<tcache_slot_opengl>(handle);
GL_state.Texture.SetActiveUnit(0);
GL_state.Texture.SetTarget(ts->texture_target);
GL_state.Texture.Enable(ts->texture_id);
glGenerateMipmap(ts->texture_target);
}
//
// End of GL_EXT_framebuffer_object stuff
// -----------------------------------------------------------------------------