Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/WolfireGames/overgrowth.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'Source/Graphics/textures.cpp')
-rw-r--r--Source/Graphics/textures.cpp2818
1 files changed, 2818 insertions, 0 deletions
diff --git a/Source/Graphics/textures.cpp b/Source/Graphics/textures.cpp
new file mode 100644
index 00000000..5377aef9
--- /dev/null
+++ b/Source/Graphics/textures.cpp
@@ -0,0 +1,2818 @@
+//-----------------------------------------------------------------------------
+// Name: textures.cpp
+// Developer: Wolfire Games LLC
+// Author: David Rosen
+// Description: The textures class loads, manages, and binds textures
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "textures.h"
+
+#include <Memory/allocation.h>
+
+#include <Utility/gl_util.h>
+
+#include <Compat/processpool.h>
+
+#include <Graphics/textures.h>
+#include <Graphics/shaders.h>
+#include <Graphics/graphics.h>
+#include <Graphics/graphics.h>
+#include <Graphics/textureref.h>
+#include <Graphics/ColorWheel.h>
+#include <Graphics/converttexture.h>
+#include <Graphics/vbocontainer.h>
+
+#include <Internal/datemodified.h>
+#include <Internal/stopwatch.h>
+#include <Internal/timer.h>
+#include <Internal/integer.h>
+#include <Internal/common.h>
+#include <Internal/cachefile.h>
+#include <Internal/profiler.h>
+#include <Internal/filesystem.h>
+#include <Internal/config.h>
+
+#include <Images/texture_data.h>
+#include <Images/freeimage_wrapper.h>
+#include <Images/image_export.hpp>
+
+#include <Timing/timingevent.h>
+#include <Timing/intel_gl_perf.h>
+
+#include <Asset/Asset/texturedummy.h>
+#include <Asset/assetmanager.h>
+
+#include <Compat/fileio.h>
+#include <Wrappers/glm.h>
+#include <Logging/logdata.h>
+#include <Memory/allocation.h>
+#include <Utility/assert.h>
+#include <Main/engine.h>
+
+#include <imgui.h>
+
+using namespace PhoenixTextures;
+
+vec4 GetAverageColor( const char* abs_path ) {
+ TextureData texture_data;
+ texture_data.Load(abs_path);
+
+ unsigned total_pixels = texture_data.GetWidth() *
+ texture_data.GetHeight();
+ unsigned total_bytes = total_pixels * 32 / 8;
+
+ float total_color[4] = {0};
+
+ std::vector<unsigned char> img_data;
+ img_data.resize(total_bytes);
+ texture_data.GetUncompressedData(&img_data[0]);
+ for(unsigned j=0; j<total_bytes; j+=4){
+ total_color[0] += img_data[j+0];
+ total_color[1] += img_data[j+1];
+ total_color[2] += img_data[j+2];
+ total_color[3] += img_data[j+3];
+ }
+
+ vec4 average_color;
+ average_color[0] = total_color[0]/total_pixels;
+ average_color[1] = total_color[1]/total_pixels;
+ average_color[2] = total_color[2]/total_pixels;
+ average_color[3] = total_color[3]/total_pixels;
+
+ return average_color;
+}
+
+bool IsCompressedGLFormat(GLenum format) {
+ switch (format) {
+ case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
+ case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
+ case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
+ case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
+ case GL_COMPRESSED_SRGB_S3TC_DXT1_EXT:
+ case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:
+ case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT:
+ case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
+ return true;
+ case GL_DEPTH_COMPONENT24:
+ case GL_RGB8:
+ case GL_RGBA8:
+ case GL_RGBA:
+ case GL_SRGB8:
+ case GL_SRGB8_ALPHA8:
+ return false;
+ default:
+ LOG_ASSERT(false);
+ return false;
+ }
+}
+
+bool GetLinearGLFormat(GLenum format) {
+ switch (format) {
+ case GL_COMPRESSED_SRGB_S3TC_DXT1_EXT:
+ return (bool) GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
+
+ case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:
+ return (bool) GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
+
+ case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT:
+ return (bool) GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
+
+ case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
+ return (bool) GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
+
+ case GL_SRGB8:
+ return (bool) GL_RGB8;
+
+ case GL_SRGB8_ALPHA8:
+ return (bool) GL_RGBA8;
+
+ case GL_SRGB_ALPHA:
+ return (bool) GL_RGBA;
+ }
+
+ return format;
+}
+
+
+namespace {
+#ifdef _WIN32
+ const char* worker_app_path = "OvergrowthWorker.exe";
+#endif
+#ifdef __APPLE__
+ const char* worker_app_path = "OvergrowthWorker";
+#endif
+#ifdef __LINUX__
+ #ifdef __x86_64__
+ const char* worker_app_path = "OvergrowthWorker.bin.x86_64";
+ #else
+ const char* worker_app_path = "OvergrowthWorker.bin.x86";
+ #endif
+#endif
+}
+
+/*
+struct Texture {
+int width, height, depth;
+unsigned int size; // for texture buffers
+unsigned int num_slices; // >1 only for array textures
+bool cube_map;
+GLuint gl_texture_id;
+GLuint gl_buffer_id; // for texture buffers
+std::vector<SubTexture> sub_textures;
+GLuint wrap_t, wrap_s, min_filter, mag_filter;
+int ref_count;
+bool no_mipmap;
+bool no_reduce;
+bool use_srgb;
+bool no_live_update;
+unsigned char flags;
+GLenum internal_format;
+GLenum format;
+GLenum tex_target;
+#ifdef TRACK_TEXTURE_REF
+std::list<TextureRef*> ref_ptrs;
+#endif
+void Init();
+};*/
+
+const char* CStringFromGLEnum(GLenum val) {
+ switch(val){
+ case GL_ALPHA: return "GL_ALPHA";
+ case GL_RGB: return "GL_RGB";
+ case GL_RGBA: return "GL_RGBA";
+ case GL_LUMINANCE: return "GL_LUMINANCE";
+ case GL_LUMINANCE_ALPHA: return "GL_LUMINANCE_ALPHA";
+
+ case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: return "GL_COMPRESSED_RGBA_S3TC_DXT5_EXT";
+ case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT: return "GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT";
+
+ case GL_RGBA32F: return "GL_RGBA32F";
+ case GL_R32UI: return "GL_R32UI";
+ case GL_RGBA16F: return "GL_RGBA16F";
+ case GL_RGB16F: return "GL_RGB16F";
+ case GL_RGB32F: return "GL_RGB32F";
+ case GL_DEPTH_COMPONENT: return "GL_DEPTH_COMPONENT";
+ case GL_DEPTH_COMPONENT24: return "GL_DEPTH_COMPONENT24";
+
+ case GL_UNSIGNED_BYTE: return "GL_UNSIGNED_BYTE";
+ case GL_FLOAT: return "GL_FLOAT";
+
+ case GL_NEAREST: return "GL_NEAREST";
+ case GL_LINEAR: return "GL_LINEAR";
+ case GL_NEAREST_MIPMAP_NEAREST: return "GL_NEAREST_MIPMAP_NEAREST";
+ case GL_LINEAR_MIPMAP_NEAREST: return "GL_LINEAR_MIPMAP_NEAREST";
+ case GL_NEAREST_MIPMAP_LINEAR: return "GL_NEAREST_MIPMAP_LINEAR";
+ case GL_LINEAR_MIPMAP_LINEAR: return "GL_LINEAR_MIPMAP_LINEAR";
+
+ case GL_CLAMP: return "GL_CLAMP";
+ case GL_REPEAT: return "GL_REPEAT";
+ case GL_CLAMP_TO_EDGE: return "GL_CLAMP_TO_EDGE";
+
+ case GL_RGB4: return "GL_RGB4";
+ case GL_RGB5: return "GL_RGB5";
+ case GL_RGB8: return "GL_RGB8";
+ case GL_RGB10: return "GL_RGB10";
+ case GL_RGB12: return "GL_RGB12";
+ case GL_RGB16: return "GL_RGB16";
+ case GL_RGBA2: return "GL_RGBA2";
+ case GL_RGBA4: return "GL_RGBA4";
+ case GL_RGB5_A1: return "GL_RGB5_A1";
+ case GL_RGBA8: return "GL_RGBA8";
+ case GL_RGB10_A2: return "GL_RGB10_A2";
+ case GL_RGBA12: return "GL_RGBA12";
+ case GL_RGBA16: return "GL_RGBA16";
+
+ case 0: return "NULL";
+ default: return "Unknown GL enum";
+ }
+}
+
+void Textures::DrawImGuiDebug() {
+ enum TexType {
+ kSimple, kArray, kCubemap, kBuffer, kUnknown
+ };
+
+ if(ImGui::TreeNode("Textures", "Textures: %d", (int)textures.size())){
+ const char* color_tex_num = "\033FFFFFFFF";
+ const char* color_cubemap = "\03399ecffFF";
+ const char* color_array = "\03399ff87FF";
+ const char* color_simple = "\033d1d1d1FF";
+ const char* color_buffer = "\033bea0ffFF";
+ const char* color_default = "\033FFFFFFFF";
+ const char* color_label = "\0338dff87FF";
+ const char* color_value = "\033FFFFFFFF";
+ const char* color_enum = "\0339ccbd6FF";
+
+ const int kBufSize = 256;
+ char buf[kBufSize];
+ for(int i=0, len=textures.size(); i<len; ++i){
+ const Texture& tex = textures[i];
+ bool drawable = false;
+ TexType type;
+ if(tex.cube_map){
+ FormatString(buf, kBufSize, "%s%d:%s cubemap", color_tex_num, i, color_cubemap);
+ type = kCubemap;
+ } else if(tex.num_slices>1){
+ FormatString(buf, kBufSize, "%s%d:%s texture array (%d slices)", color_tex_num, i, color_array, tex.num_slices);
+ type = kArray;
+ } else if(tex.sub_textures.size() == 1 && !tex.sub_textures[0].texture_name.empty()){
+ FormatString(buf, kBufSize, "%s%d:%s %s", color_tex_num, i, color_simple, tex.sub_textures[0].texture_name.c_str());
+ drawable = true;
+ type = kSimple;
+ } else if(tex.gl_buffer_id != UNLOADED_ID){
+ FormatString(buf, kBufSize, "%s%d:%s texture buffer", color_tex_num, i, color_buffer);
+ type = kBuffer;
+ } else if(tex.gl_texture_id != UNLOADED_ID){
+ FormatString(buf, kBufSize, "%s%d:%s generated texture", color_tex_num, i, color_simple);
+ drawable = true;
+ type = kSimple;
+ } else {
+ FormatString(buf, kBufSize, "%s%d: unknown", color_tex_num, i);
+ type = kUnknown;
+ }
+ if(!tex.name.empty()){
+ const char* color = color_default;
+ switch(type){
+ case kSimple: color = color_simple; break;
+ case kArray: color = color_array; break;
+ case kCubemap: color = color_cubemap; break;
+ case kBuffer: color = color_buffer; break;
+ case kUnknown: break;
+ }
+ FormatString(buf, kBufSize, "%s%d:%s %s", color_tex_num, i, color, tex.name.c_str());
+ }
+
+ if(type != kUnknown){
+ ImGui::PushID(i);
+ if(ImGui::TreeNode("","%s",buf)){
+ ImGui::PopID();
+ ImGui::Indent(ImGui::GetTreeNodeToLabelSpacing());
+ ImGui::Text("%sReferences:%s %d", color_label, color_value, tex.ref_count);
+ if(type == kBuffer){
+ ImGui::Text("%sSize: %s%d", color_label, color_value, tex.size);
+ ImGui::Text("%sFormat: %s%s", color_label, color_value, CStringFromGLEnum(tex.internal_format));
+ } else if(type == kArray){
+ ImGui::Text("%sSize: %s%d x %d x %d", color_label, color_value, tex.width, tex.height, tex.num_slices);
+ } else if(type == kSimple || type == kCubemap){
+ ImGui::Text("%sSize: %s%d x %d", color_label, color_value, tex.width, tex.height);
+ }
+ if(type == kArray || type == kSimple || type == kCubemap){
+ ImGui::Text("%s%s / %s", color_enum, CStringFromGLEnum(tex.internal_format), CStringFromGLEnum(tex.format));
+ ImGui::Text("%s%s / %s", color_enum, CStringFromGLEnum(tex.wrap_s), CStringFromGLEnum(tex.wrap_t));
+ ImGui::Text("%s%s / %s", color_enum, CStringFromGLEnum(tex.min_filter), CStringFromGLEnum(tex.mag_filter));
+ bool temp;
+ temp = tex.use_srgb;
+ ImGui::Checkbox("sRGB", &temp);
+ temp = !tex.no_mipmap;
+ ImGui::Checkbox("Mipmaps", &temp);
+ }
+ ImGui::Unindent(ImGui::GetTreeNodeToLabelSpacing());
+ ImGui::TreePop();
+ } else {
+ ImGui::PopID();
+ if (ImGui::IsItemHovered() && drawable) {
+ if( tex.format != GL_DEPTH_COMPONENT ) {
+ ImGui::PushStyleColor(ImGuiCol_PopupBg, ImVec4(0.5, 0.5, 1.0, 0.5));
+ ImGui::BeginTooltip();
+ ImGui::PushTextWrapPos(450.0f);
+ Textures::Instance()->EnsureInVRAM(i);
+ ImGui::Image((ImTextureID)((uintptr_t)textures[i].gl_texture_id), ImVec2(128,128), ImVec2(0,0), ImVec2(1,1));
+ ImGui::PopTextWrapPos();
+ ImGui::EndTooltip();
+ ImGui::PopStyleColor(1);
+ }
+ }
+ }
+ }
+ }
+ ImGui::TreePop();
+ }
+}
+
+Textures::Textures() {
+ process_pool = new ProcessPool(worker_app_path);
+ PROFILED_TEXTURE_MUTEX_LOCK
+ for(unsigned int i=0; i<_texture_units; i++){
+ bound_texture[i]=INVALID_ID;
+ }
+ m_wrap_t = _default_wrap;
+ m_wrap_s = _default_wrap;
+ min_filter = _default_min;
+ mag_filter = _default_max;
+ active_texture = 0;
+ // TODO: Dispose thread pool when deleting
+
+ if (config["save_as_crunch"].toNumber<bool>()) {
+ convert_file_type = "_converted.crn";
+ } else {
+ convert_file_type = "_converted.dds";
+ }
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+}
+
+static const float _inv_gamma = 1.0f/2.2f;
+static const float float_to_byte = 255.0f;
+static const float byte_to_float = 1.0f/float_to_byte;
+
+const int kCubeMapHDRVersion = 2;
+
+void Textures::SaveCubeMapMipmapsHDR (TextureRef which, const char* filename, int width) {
+ Textures::Instance()->bindTexture(which, 0);
+ int mip_level = 0;
+ int old_width = width;
+ std::vector<float> data;
+ while(width > 1){
+ for (int i=0; i<6; i++) {
+ int start_size = data.size();
+ data.resize(data.size() + width * width * 4);
+ glGetTexImage( GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, mip_level, GL_BGRA, GL_FLOAT, &data[start_size] );
+ }
+ ++mip_level;
+ width/=2;
+ }
+ std::string path = std::string(filename)+".hdrcube";
+ CreateParentDirs(path.c_str());
+ FILE *cache_file = my_fopen(path.c_str(), "wb");
+ if(cache_file){
+ fwrite(&kCubeMapHDRVersion, sizeof(int), 1, cache_file);
+ {
+ int size = data.size();
+ fwrite(&size, sizeof(int), 1, cache_file);
+ }
+ fwrite(&old_width, sizeof(int), 1, cache_file);
+ fwrite(&data[0], sizeof(float), data.size(), cache_file);
+ fclose(cache_file);
+ }
+}
+
+
+TextureRef Textures::LoadCubeMapMipmapsHDR (const char* filename) {
+ TextureRef tex;
+ FILE *cache_file = my_fopen(filename, "rb");
+ if(cache_file){
+ int version;
+ fread(&version, sizeof(int), 1, cache_file);
+ if(version == kCubeMapHDRVersion){
+ int size, width;
+ fread(&size, sizeof(int), 1, cache_file);
+ fread(&width, sizeof(int), 1, cache_file);
+ float* data = (float*)alloc.stack.Alloc(sizeof(float) * size);
+ fread(&data[0], sizeof(float), size, cache_file);
+ fclose(cache_file);
+ tex = Textures::Instance()->makeCubemapTexture(128, 128, GL_RGBA16F, GL_RGBA, Textures::MIPMAPS);
+ Textures::Instance()->SetTextureName(tex, filename);
+ int mip_level = 0;
+ int index = 0;
+ while(width > 1){
+ for (int i=0; i<6; i++) {
+ glTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, mip_level, 0, 0, width, width, GL_BGRA, GL_FLOAT, &data[index]);
+ index += width * width * 4;
+ }
+ ++mip_level;
+ width/=2;
+ }
+ alloc.stack.Free(data);
+ }
+ }
+ return tex;
+}
+
+void Texture::Init() {
+ width = 0;
+ height = 0;
+ size = 0;
+ num_slices = 1;
+ cube_map = false;
+ gl_texture_id = UNLOADED_ID;
+ gl_buffer_id = UNLOADED_ID;
+ wrap_t = 0;
+ wrap_s = 0;
+ min_filter = 0;
+ mag_filter = 0;
+ ref_count = 1;
+ no_mipmap = false;
+ no_reduce = false;
+ use_srgb = false;
+ no_live_update = true;
+ flags = 0;
+ internal_format = GL_NONE;
+ format = GL_NONE;
+ tex_target = GL_NONE;
+ sub_textures.clear();
+ name.clear();
+}
+
+//#pragma optimize(", off)
+void Textures::DisposeTexture(int which, bool free_space) {
+ Texture &texture = textures[which];
+
+ if( texture.notify_on_dispose ) {
+ LOGI << "Disposed texture " << texture.name << " with gl id " << texture.gl_texture_id << std::endl;
+ }
+
+ for (unsigned int i = 0; i < texture.sub_textures.size(); i++) {
+ delete texture.sub_textures[i].texture_data;
+ }
+
+ texture.sub_textures.clear();
+
+ RemoveTextureFromVRAM(which);
+
+ texture.width = 0;
+ texture.height = 0;
+ texture.size = 0;
+ texture.num_slices = 1;
+ texture.name = "Disposed";
+ texture.ref_count = 0;
+ texture.gl_texture_id = UNLOADED_ID;
+ texture.gl_buffer_id = UNLOADED_ID;
+
+ if(free_space){
+ free_spaces.push_back(which);
+ }
+}
+//#pragma optimize("", on)
+
+void Textures::Dispose() {
+ blank_texture_ref.clear();
+ blank_normal_texture_ref.clear();
+
+ detail_color_texture_ref.clear();
+ detail_normal_texture_ref.clear();
+
+ PROFILED_TEXTURE_MUTEX_LOCK
+
+ for (unsigned int i=0;i<textures.size();i++){
+ DisposeTexture(i);
+ }
+ textures.clear();
+
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+}
+
+int conversion_num = 0;
+
+void Textures::ReloadInternal() {
+ for (unsigned int i=0;i<textures.size();i++){
+ Texture &texture = textures[i];
+
+ if (texture.no_live_update) {
+ continue;
+ }
+
+ std::vector<unsigned int>::const_iterator it = std::find(free_spaces.begin(), free_spaces.end(), i);
+ if (it != free_spaces.end()) {
+ // no texture in this slot
+ LOG_ASSERT(texture.width == 0);
+ LOG_ASSERT(texture.height == 0);
+ LOG_ASSERT(texture.sub_textures.empty());
+ LOG_ASSERT(texture.gl_texture_id == UNLOADED_ID);
+ LOG_ASSERT(texture.gl_buffer_id == UNLOADED_ID);
+ continue;
+ }
+
+ LOG_ASSERT(!texture.sub_textures.empty());
+ char abs_path[kPathSize];
+
+ if (texture.sub_textures.size() == 1) {
+ // ordinary texture
+ LOG_ASSERT(texture.num_slices == 1);
+
+ SubTexture &sub = texture.sub_textures[0];
+ LOG_ASSERT(!sub.texture_name.empty());
+
+ if (FindImagePath(sub.texture_name.c_str(), abs_path, kPathSize, kDataPaths | kModPaths | kWriteDir | kModWriteDirs, false) == 0){
+ if(strcmp(abs_path, sub.load_name.c_str()) == 0 && GetDateModifiedInt64(abs_path) == sub.orig_modified){
+ continue;
+ }
+ }else{
+ continue;
+ }
+
+ std::string name = sub.texture_name;
+ unsigned char flags = texture.flags;
+ DisposeTexture(i, false);
+ loadTexture(name, i, flags | PX_NOCONVERT);
+ } else {
+ // array texture
+ LOG_ASSERT(texture.num_slices > 1);
+
+ LogSystem::LogData(LogSystem::debug, "tex", __FILE__, __LINE__) << "Checking if array texture " << i << " (with " << texture.sub_textures.size() << " slices) needs reload..." << std::endl;
+
+ bool needs_reload = false;
+
+ for (unsigned int s = 0; s < texture.sub_textures.size(); s++) {
+ SubTexture &sub = texture.sub_textures[s];
+ LOG_ASSERT(!sub.texture_name.empty());
+ ModID modsource;
+ if (FindImagePath(sub.texture_name.c_str(), abs_path, kPathSize, kAnyPath, false,NULL,true, &modsource) == 0){
+ if(strcmp(abs_path, sub.load_name.c_str()) == 0 && GetDateModifiedInt64(abs_path) == sub.orig_modified){
+ LogSystem::LogData(LogSystem::debug, "tex", __FILE__, __LINE__) << " slice " << s << ": \"" << sub.texture_name << "\" no" << std::endl;
+ } else {
+ LogSystem::LogData(LogSystem::debug, "tex", __FILE__, __LINE__) << " slice " << s << ": \"" << sub.texture_name << "\" yes" << std::endl;
+ delete sub.texture_data;
+ std::string dst_path = AssemblePath(GetWritePath(modsource), SanitizePath(sub.texture_name + convert_file_type));
+ ConvertImage(abs_path, dst_path, GetTempDDSPath(dst_path, true, conversion_num), TextureData::Fast);
+ sub.texture_data = new TextureData();
+ sub.texture_data->Load(dst_path.c_str());
+ sub.orig_modified = GetDateModifiedInt64(abs_path);
+ needs_reload = true;
+ texture.width = 0;
+ texture.height = 0;
+ }
+ } else {
+ LogSystem::LogData(LogSystem::warning, "tex", __FILE__, __LINE__) << " slice " << s << ": \"" << sub.texture_name << "\" no longer exists!" << std::endl;
+ }
+ }
+ if (needs_reload) {
+ RemoveTextureFromVRAM(i);
+ }
+ }
+ }
+}
+
+void Textures::Reload() {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ ReloadInternal();
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+}
+
+//Set texture to wrap or clamp to edge
+void Textures::setWrap(GLenum wrap) {
+ m_wrap_s = wrap;
+ m_wrap_t = wrap;
+}
+
+void Textures::setWrap(GLenum wrap_s, GLenum wrap_t) {
+ m_wrap_s = wrap_s;
+ m_wrap_t = wrap_t;
+}
+
+//Set minification and magnification filters
+void Textures::setFilters(GLenum min, GLenum mag) {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ min_filter = min;
+ mag_filter = mag;
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+}
+
+void GetCompletePath(std::string *path_utf8_ptr) {
+ std::string &path_utf8 = *path_utf8_ptr;
+ const int buf_size = 2048;
+ #ifdef _WIN32
+ WCHAR path_utf16[buf_size];
+ if(!MultiByteToWideChar(CP_UTF8, 0, path_utf8.c_str(), -1, path_utf16, buf_size)){
+ FatalError("Error", "Error converting utf8 string to utf16: %s", path_utf8.c_str());
+ }
+ WCHAR full_path_utf16[buf_size];
+ GetFullPathNameW(path_utf16, 2048, full_path_utf16, NULL);
+ char full_path_utf8[buf_size];
+ if(!WideCharToMultiByte(CP_UTF8, 0, full_path_utf16, -1, full_path_utf8, buf_size, NULL, NULL)){
+ DWORD err = GetLastError();
+ FatalError("Error", "Error converting utf16 string to utf8.");
+ }
+ int str_len = strlen(full_path_utf8);
+ for(int i=0; i<str_len; ++i){
+ if(full_path_utf8[i] == '\\'){
+ full_path_utf8[i] = '/';
+ }
+ }
+ path_utf8 = full_path_utf8;
+ #else
+ char full_path_utf8[buf_size];
+ realpath(path_utf8.c_str(), full_path_utf8);
+ path_utf8 = full_path_utf8;
+ #endif
+}
+
+void Textures::IncrementRefCount(unsigned int id) {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ if (id != INVALID_ID && id < textures.size()) {
+ textures[id].ref_count++;
+ }
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+}
+
+void Textures::DecrementRefCount(unsigned int id) {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ if (id != INVALID_ID && id < textures.size()) {
+ textures[id].ref_count--;
+ }
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+}
+
+#ifdef TRACK_TEXTURE_REF
+void Textures::AddRefPtr(unsigned int id, TextureRef* ref) {
+ texture_mutex.lock();
+ if (id != INVALID_ID && id < textures.size()) {
+ textures[id].ref_ptrs.push_back(ref);
+ }
+ texture_mutex.unlock();
+}
+
+void Textures::RemoveRefPtr(unsigned int id, TextureRef* ref) {
+ texture_mutex.lock();
+ if (id != INVALID_ID && id < textures.size()) {
+ textures[id].ref_ptrs.erase(
+ std::find(textures[id].ref_ptrs.begin(),
+ textures[id].ref_ptrs.end(),
+ ref));
+ }
+ texture_mutex.unlock();
+}
+#endif
+
+void Textures::GenerateMipmap(const TextureRef &tex_ref) {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Textures::GenerateMipmap");
+ bindTexture(tex_ref,0);
+ glGenerateMipmap(GL_TEXTURE_2D);
+}
+
+void Textures::setSampleFilter( const TextureRef &tex_ref, enum SampleFilter filter )
+{
+ bindTexture(tex_ref);
+ if( filter == FILTER_NEAREST )
+ {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ }
+ else if( filter == FILTER_LINEAR )
+ {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ }
+}
+
+void Textures::setSampleWrap( const TextureRef &tex_ref, enum SampleWrap wrapping )
+{
+ bindTexture(tex_ref);
+
+ if( wrapping == WRAP_CLAMP_TO_EDGE )
+ {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ }
+}
+
+void Textures::subImage( const TextureRef &tex_ref, void* data )
+{
+ if( tex_ref.valid() )
+ {
+ bindTexture( tex_ref );
+ Texture &texture = textures[tex_ref.id];
+
+ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texture.width, texture.height, texture.format, GL_UNSIGNED_BYTE,data);
+ }
+ else
+ {
+ LOGE << "Unable to load data to texture." << std::endl;
+ }
+}
+
+void Textures::subImage( const TextureRef &destination, const TextureRef &source, int xoffset, int yoffset )
+{
+ if( destination.valid() && source.valid() )
+ {
+ Texture &source_tex = textures[source.id];
+ Texture &dst_tex = textures[destination.id];
+ if(source_tex.sub_textures.empty()){
+ DisplayError("Error", "Sub image has no sub_textures");
+ return;
+ }
+ TextureData* tex_data = source_tex.sub_textures[0].texture_data;
+ if(!tex_data){
+ DisplayError("Error", "Trying to subImage from texture with no data");
+ return;
+ }
+
+ tex_data->EnsureInRAM();
+
+ if(tex_data->GetGLInternalFormat() != dst_tex.internal_format){
+ // only continue if error is because sRGB/linear mismatch
+ if (GetLinearGLFormat(tex_data->GetGLInternalFormat()) != GetLinearGLFormat(dst_tex.internal_format)) {
+ // TODO: could convert source texture to target format
+ LOGE << "Trying to subImage texture of format " << tex_data->GetGLInternalFormat() << " into texture of different format " << dst_tex.internal_format << std::endl;
+ return;
+ }
+ }
+
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Textures::subImage");
+ Graphics::Instance()->DebugTracePrint(source_tex.sub_textures[0].texture_name.c_str());
+
+ bindTexture( destination );
+
+ bool regen_mipmap = (false == textures[destination.id].no_mipmap);
+
+ if(tex_data->IsCompressed()) {
+ int reduction_factor = Graphics::Instance()->config_.texture_reduction_factor();
+ int dataMipLevel = 0;
+ int mipLevels = tex_data->GetMipLevels();
+ LOG_ASSERT(mipLevels > 0);
+
+ int targetWidth = tex_data->GetWidth() / reduction_factor;
+ int targetHeight = tex_data->GetHeight() / reduction_factor;
+
+ if(targetWidth < 4)
+ targetWidth = 4;
+ if(targetHeight < 4)
+ targetHeight = 4;
+
+ for(int i = 0; i < mipLevels; ++i) {
+ int width = tex_data->GetMipWidth(dataMipLevel);
+ int height = tex_data->GetMipHeight(dataMipLevel);
+
+ if (width > targetWidth && height > targetHeight) {
+ dataMipLevel++;
+ } else {
+ break;
+ }
+ }
+
+ for (int i = 0; i < mipLevels; i++) {
+ int width = tex_data->GetMipWidth(dataMipLevel);
+ int height = tex_data->GetMipHeight(dataMipLevel);
+
+ if(width < 4 || height < 4) {
+ // TODO: we should also set TEXTURE_MAX_LEVEL
+ // so we don't end up using the mip levels which are not valid
+ // currently since this is only used by decals and those are 512x512
+ // you really shouldn't get far enough away that it matters
+ break;
+ }
+
+ glCompressedTexSubImage2D(GL_TEXTURE_2D, i, xoffset, yoffset, width, height, dst_tex.internal_format, tex_data->GetMipDataSize(0, dataMipLevel), tex_data->GetMipData(0, dataMipLevel));
+
+ if(dataMipLevel + 1 < mipLevels)
+ dataMipLevel++;
+
+ xoffset = (xoffset + 1) / 2;
+ yoffset = (yoffset + 1) / 2;
+ }
+ regen_mipmap = false;
+ } else {
+ glTexSubImage2D(GL_TEXTURE_2D, 0, xoffset, yoffset, tex_data->GetMipWidth(0), tex_data->GetMipHeight(0), tex_data->GetGLBaseFormat(), tex_data->GetGLType(), tex_data->GetMipData(0, 0));
+ }
+
+ if( regen_mipmap )
+ {
+ glGenerateMipmap(GL_TEXTURE_2D);
+ }
+ } else {
+ LOGE << __FUNCTION__ << ": Source or destination is invalid" << std::endl;
+ }
+}
+
+void Textures::TextureToVRAM(unsigned int which) {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "TextureToVRAM");
+ CHECK_GL_ERROR();
+
+ if (!deferred_delete_textures.empty()) {
+ for (std::list<GLuint>::iterator it = deferred_delete_textures.begin(); it != deferred_delete_textures.end(); it++) {
+ GLuint tex_id = *it;
+ LOGI << "Deferred delete of texture " << tex_id << std::endl;
+ glDeleteTextures(1, &tex_id);
+ }
+ deferred_delete_textures.clear();
+ }
+
+ Texture& tex = textures[which];
+
+ if(tex.sub_textures.empty()){
+ LOGW << "Calling TextureToVRAM on a texture with no sub_textures" << std::endl;
+ return;
+ }
+
+ Graphics::Instance()->DebugTracePrint(tex.sub_textures[0].texture_name.c_str());
+
+ bool tex_compressed = tex.sub_textures[0].texture_data->IsCompressed();
+ // TODO: silently fix the issue
+ for(int i=1, len=tex.sub_textures.size(); i<len; ++i){
+ const SubTexture &s = tex.sub_textures[i];
+ if(s.texture_data->IsCompressed() != tex_compressed) {
+ LOGE << "TextureToVRAM failed (compression mismatch) for " << s.load_name << std::endl;
+ return;
+ }
+ }
+
+ if(tex.gl_texture_id != UNLOADED_ID)return;
+
+ for( unsigned i = 0; i < tex.sub_textures.size(); i++ ) {
+ tex.sub_textures[i].texture_data->EnsureInRAM();
+ }
+
+ bool is_array = (tex.sub_textures.size() > 1);
+ bool is_cube = tex.sub_textures[0].texture_data->IsCube();
+
+ if (is_array && is_cube) {
+ // GL 4.0 feature, we have minimum requirement 3.2
+ LOGE << "cube map arrays not supported" << std::endl;
+ return;
+ }
+
+ // FIXME?
+ if (tex.use_srgb) {
+ if (tex.sub_textures[0].texture_data->GetColorSpace() != TextureData::sRGB) {
+ //LOGW << "Texture " << tex.sub_textures[0].load_name << " uses sRGB color space but does not specify it in file name" << std::endl;
+ }
+ tex.sub_textures[0].texture_data->SetColorSpace(TextureData::sRGB);
+ } else {
+ if (tex.sub_textures[0].texture_data->GetColorSpace() != TextureData::Linear) {
+ //LOGW << "Texture " << tex.sub_textures[0].load_name << " specifies sRGB color space in file name but linear internally" << std::endl;
+ }
+ }
+
+ // TODO fill this here
+ GLenum& internal_format = tex.internal_format;
+
+ unsigned int num_mips = tex.sub_textures[0].texture_data->GetMipLevels();
+
+ if (is_array) {
+ // set texture size to size of smallest slice
+ LOG_ASSERT(tex.width == 0);
+ LOG_ASSERT(tex.height == 0);
+
+ unsigned int width = tex.sub_textures[0].texture_data->GetWidth();
+ unsigned int height = tex.sub_textures[0].texture_data->GetHeight();
+
+ for (unsigned int i = 1; i < tex.sub_textures.size(); i++) {
+ width = std::min(width, tex.sub_textures[i].texture_data->GetWidth());
+ height = std::min(height, tex.sub_textures[i].texture_data->GetHeight());
+ num_mips = std::min(num_mips, tex.sub_textures[i].texture_data->GetMipLevels());
+ }
+
+ tex.width = width;
+ tex.height = height;
+ }
+
+ if(!tex.no_reduce){
+ tex.width /= Graphics::Instance()->config_.texture_reduction_factor();
+ tex.height /= Graphics::Instance()->config_.texture_reduction_factor();
+ }
+
+ glGenTextures( 1, &tex.gl_texture_id );
+ LOG_ASSERT(tex.gl_texture_id != INVALID_ID);
+ LOG_ASSERT(tex.gl_texture_id != UNLOADED_ID);
+
+ if( tex_compressed == false ) {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Uncompressed");
+ tex.tex_target = is_array ? GL_TEXTURE_2D_ARRAY : GL_TEXTURE_2D;
+ glBindTexture(tex.tex_target, tex.gl_texture_id);
+
+ if(!tex.no_mipmap && num_mips > 1){
+ glTexParameteri(tex.tex_target, GL_TEXTURE_MIN_FILTER, tex.min_filter);
+ } else {
+ glTexParameteri(tex.tex_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ }
+ glTexParameteri(tex.tex_target, GL_TEXTURE_MAG_FILTER, tex.mag_filter);
+
+ glTexParameteri(tex.tex_target, GL_TEXTURE_WRAP_S, tex.wrap_s);
+ glTexParameteri(tex.tex_target, GL_TEXTURE_WRAP_T, tex.wrap_t);
+
+ if(Graphics::Instance()->config_.anisotropy())
+ glTexParameterf(tex.tex_target, GL_TEXTURE_MAX_ANISOTROPY_EXT, Graphics::Instance()->config_.anisotropy());
+
+ CHECK_GL_ERROR();
+
+ internal_format = GL_RGBA;
+
+ //unsigned int totalTextureSize = 0;
+ if (is_array) {
+ TextureData* tex_data = tex.sub_textures[0].texture_data;
+ tex.internal_format = tex_data->GetGLInternalFormat();
+ tex.format = GL_BGRA;
+ glTexImage3D(tex.tex_target, 0, tex_data->GetGLInternalFormat(), tex_data->GetWidth(), tex_data->GetHeight(), tex.num_slices, 0, GL_BGRA, tex_data->GetGLType(), NULL);
+ }
+
+ for (unsigned int slice = 0; slice < tex.num_slices; slice++) {
+ TextureData* tex_data = tex.sub_textures[slice].texture_data;
+ // TODO: check formats
+ if(!tex_data){
+ FatalError("Error", "Compressed slice in uncompressed texture array");
+ }
+
+ tex_data->EnsureInRAM();
+
+ if (!tex.no_mipmap && !tex_data->HasMipmaps()) {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "GenerateMipmaps");
+ bool success = tex_data->GenerateMipmaps();
+ if (!success) {
+ FatalError("Error", "Mipmap generation failed");
+ }
+ assert(tex_data->HasMipmaps());
+ }
+
+ unsigned int startMip = 0;
+ while ( startMip < tex_data->GetMipLevels() && (int)tex_data->GetMipWidth(startMip) > tex.width ) {
+ startMip++;
+ }
+
+ internal_format = tex_data->GetGLInternalFormat();
+ if (is_array) {
+ tex.format = tex_data->GetGLBaseFormat();
+ LOG_ASSERT(tex_data->GetWidth() == tex.sub_textures[0].texture_data->GetWidth() &&
+ tex_data->GetHeight() == tex.sub_textures[0].texture_data->GetHeight());
+ for (unsigned int i = startMip; i < num_mips; i++ ) {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "glTexSubImage3D");
+ glTexSubImage3D(tex.tex_target, i - startMip, 0, 0, slice, tex_data->GetMipWidth(i), tex_data->GetMipHeight(i), 1,
+ tex_data->GetGLBaseFormat(),
+ tex_data->GetGLType(),
+ tex_data->GetMipData(0, i));
+ }
+ } else {
+ tex.format = tex_data->GetGLBaseFormat();
+ for (unsigned int i = startMip; i < num_mips; i++ ) {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "glTexImage2D");
+ glTexImage2D(tex.tex_target,
+ i - startMip,
+ internal_format,
+ tex_data->GetMipWidth(i),
+ tex_data->GetMipHeight(i),
+ 0,
+ tex_data->GetGLBaseFormat(),
+ tex_data->GetGLType(),
+ tex_data->GetMipData(0, i));
+ }
+ }
+ //totalTextureSize = 4 * tex_data->GetWidth() * tex_data->GetHeight();
+ }
+
+ CHECK_GL_ERROR();
+ } else {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Compressed");
+ int skip_levels = 0;
+ if(!tex.no_reduce){
+ skip_levels = Graphics::Instance()->config_.texture_reduce();
+ }
+
+ if(is_cube) {
+ tex.tex_target = GL_TEXTURE_CUBE_MAP;
+ glBindTexture(tex.tex_target, tex.gl_texture_id);
+
+ glTexParameteri(tex.tex_target, GL_TEXTURE_MIN_FILTER, tex.min_filter);
+ glTexParameteri(tex.tex_target, GL_TEXTURE_MAG_FILTER, tex.mag_filter);
+
+ glTexParameteri(tex.tex_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(tex.tex_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexParameteri(tex.tex_target, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
+
+ tex.cube_map = true;
+
+ if(Graphics::Instance()->config_.anisotropy()) {
+ glTexParameterf(tex.tex_target, GL_TEXTURE_MAX_ANISOTROPY_EXT, Graphics::Instance()->config_.anisotropy());
+ }
+
+ TextureData* tex_data = tex.sub_textures[0].texture_data;
+
+ LOG_ASSERT(tex_data);
+
+ internal_format = tex_data->GetGLInternalFormat();
+
+ tex_data->EnsureInRAM();
+
+ uint32_t totalTextureSize = 0;
+ for(unsigned int j = 0; j < tex_data->GetNumFaces(); j++) {
+ for(unsigned int i = 0; i < tex_data->GetMipLevels(); i++) {
+ int width = tex_data->GetMipWidth(i);
+ int height = tex_data->GetMipHeight(i);
+ if( width == 0 || height == 0 ) {
+ break;
+ }
+ if( (int)i>=skip_levels ) {
+ if(tex_data->IsCompressed()) {
+ glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + j,
+ i-skip_levels,
+ internal_format,
+ width,
+ height,
+ 0,
+ tex_data->GetMipDataSize(j, i),
+ tex_data->GetMipData(GL_TEXTURE_CUBE_MAP_POSITIVE_X + j, i));
+ } else {
+ glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + j,
+ i-skip_levels,
+ internal_format,
+ width,
+ height,
+ 0,
+ tex_data->GetGLBaseFormat(),
+ tex_data->GetGLType(),
+ tex_data->GetMipData(GL_TEXTURE_CUBE_MAP_POSITIVE_X + j, i));
+ }
+ totalTextureSize += tex_data->GetMipDataSize(j, i);
+ }
+ }
+ }
+
+ LOG_ASSERT(tex.sub_textures.size() == 1);
+ } else {
+ LOG_ASSERT(!tex.sub_textures.empty());
+ tex.tex_target = is_array ? GL_TEXTURE_2D_ARRAY : GL_TEXTURE_2D;
+
+ glBindTexture(tex.tex_target, tex.gl_texture_id);
+
+ glTexParameteri(tex.tex_target, GL_TEXTURE_MIN_FILTER, tex.min_filter);
+ glTexParameteri(tex.tex_target, GL_TEXTURE_MAG_FILTER, tex.mag_filter);
+
+ glTexParameteri(tex.tex_target, GL_TEXTURE_WRAP_S, tex.wrap_s);
+ glTexParameteri(tex.tex_target, GL_TEXTURE_WRAP_T, tex.wrap_t);
+
+ if(Graphics::Instance()->config_.anisotropy())
+ glTexParameterf(tex.tex_target, GL_TEXTURE_MAX_ANISOTROPY_EXT, Graphics::Instance()->config_.anisotropy());
+
+
+ if (!is_array)
+ {
+ TextureData* tex_data = tex.sub_textures[0].texture_data;
+
+ LOG_ASSERT(tex_data);
+
+ internal_format = tex_data->GetGLInternalFormat();
+
+ tex_data->EnsureInRAM();
+
+ int width = tex_data->GetWidth();
+ int height = tex_data->GetHeight();
+
+ uint32_t totalTextureSize = 0;
+ int temp_skip_levels = skip_levels + 0;
+ for(unsigned int i = 0; i < tex_data->GetMipLevels(); i++)
+ {
+ if( width == 0 && height == 0 ) {
+ break;
+ }
+ if( (int)i >= temp_skip_levels ) {
+ if(tex_data->IsCompressed()) {
+ glCompressedTexImage2D(tex.tex_target,
+ i-temp_skip_levels,
+ internal_format,
+ tex_data->GetMipWidth(i),
+ tex_data->GetMipHeight(i),
+ 0,
+ tex_data->GetMipDataSize(0, i),
+ tex_data->GetMipData(0, i));
+ } else {
+ glTexImage2D(tex.tex_target,
+ i-temp_skip_levels,
+ internal_format,
+ width,
+ height,
+ 0,
+ tex_data->GetGLBaseFormat(),
+ tex_data->GetGLType(),
+ tex_data->GetMipData(0, i));
+ }
+ CHECK_GL_ERROR();
+ totalTextureSize += tex_data->GetMipDataSize(0, i);
+ }
+ }
+ } else {
+ // array texture
+ TextureData* tex_data = tex.sub_textures[0].texture_data;
+
+ LOG_ASSERT(tex_data);
+
+ int width = tex.width;
+ int height = tex.height;
+
+ internal_format = tex_data->GetGLInternalFormat();
+
+ tex_data->EnsureInRAM();
+
+ for (unsigned int mip = 0; mip < num_mips; mip++) {
+ assert( tex.tex_target == GL_TEXTURE_3D || tex.tex_target == GL_TEXTURE_2D_ARRAY );
+ int image_size = tex_data->GetMipDataSize(0, mip + tex_data->GetMipLevels() - num_mips) * tex.num_slices;
+ glTexImage3D(tex.tex_target,
+ mip,
+ internal_format,
+ width,
+ height,
+ tex.num_slices,
+ 0,
+ GL_RGBA,
+ GL_UNSIGNED_BYTE,
+ NULL);
+ width = std::max(1, (width + 1) / 2);
+ height = std::max(1, (height + 1) / 2);
+ }
+
+ for (unsigned int slice = 0; slice < tex.num_slices; slice++) {
+ TextureData* tex_data = tex.sub_textures[slice].texture_data;
+
+ int width = tex_data->GetWidth();
+ int height = tex_data->GetHeight();
+
+ LOG_ASSERT(tex_data->IsCompressed());
+ LOG_ASSERT(num_mips <= tex_data->GetMipLevels());
+
+ // in case the slice is larger than others find the starting level
+ unsigned int start_mip;
+ for (start_mip = 0; start_mip < tex_data->GetMipLevels(); start_mip++) {
+ if (width == tex.width && height == tex.height) {
+ break;
+ }
+ width = std::max(1, (width + 1) / 2);
+ height = std::max(1, (height + 1) / 2);
+ }
+
+ for (unsigned int i = start_mip; i < tex_data->GetMipLevels(); i++) {
+ glCompressedTexSubImage3D(tex.tex_target,
+ i - start_mip,
+ 0,
+ 0,
+ slice,
+ width,
+ height,
+ 1,
+ internal_format,
+ tex_data->GetMipDataSize(0, i),
+ tex_data->GetMipData(0, i));
+
+ width = std::max(1, (width + 1) / 2);
+ height = std::max(1, (height + 1) / 2);
+ }
+ }
+ }
+ glBindTexture(tex.tex_target, 0);
+ }
+ }
+
+ if(tex.gl_texture_id == UNLOADED_ID) {
+ DisplayError("Error","Texture ID is overlapping with UNLOADED_ID");
+ }
+
+ RebindTexture(GetActiveTexture());
+
+
+ if(config["texture_minimize_ram"].toBool() == true){
+ //LOGI << "Unloading texture " << tex.sub_textures[0].texture_data->GetPath() << " from RAM to save space" << std::endl;
+ tex.sub_textures[0].texture_data->UnloadData();
+ }
+
+ LOG_ASSERT(tex.tex_target != 0);
+
+ CHECK_GL_ERROR();
+}
+
+void Textures::drawTexture(TextureRef id, vec3 where, float size, float rotation) {
+ CHECK_GL_ERROR();
+
+ // no coloring
+ Graphics* graphics = Graphics::Instance();
+ Shaders* shaders = Shaders::Instance();
+
+ int shader_id = shaders->returnProgram("simple_2d #TEXTURE #FLIPPED");
+ shaders->createProgram(shader_id);
+ int shader_attrib_vert_coord = shaders->returnShaderAttrib("vert_coord", shader_id);
+ int shader_attrib_tex_coord = shaders->returnShaderAttrib("tex_coord", shader_id);
+ int uniform_mvp_mat = shaders->returnShaderVariable("mvp_mat", shader_id);
+ int uniform_color = shaders->returnShaderVariable("color", shader_id);
+ shaders->setProgram(shader_id);
+ CHECK_GL_ERROR();
+
+ GLState gl_state;
+ gl_state.blend = true;
+ gl_state.cull_face = false;
+ gl_state.depth_write = false;
+ gl_state.depth_test = false;
+ graphics->setGLState(gl_state);
+ CHECK_GL_ERROR();
+
+
+ glm::mat4 proj_mat;
+ proj_mat = glm::ortho(0.0f, (float)graphics->window_dims[0], 0.0f, (float)graphics->window_dims[1]);
+ glm::mat4 modelview_mat(1.0f);
+ modelview_mat = glm::translate(modelview_mat, glm::vec3(where[0]-size*0.5f,where[1]-size*0.5f,0));
+ modelview_mat = glm::scale(modelview_mat, glm::vec3(size,size,1.0f));
+ modelview_mat = glm::translate(modelview_mat, glm::vec3(0.5f,0.5f,0.5f));
+ modelview_mat = glm::rotate(modelview_mat, rotation, glm::vec3(0.0f, 0.0f, 1.0f));
+ modelview_mat = glm::translate(modelview_mat, glm::vec3(-0.5f,-0.5f,-0.5f));
+ glm::mat4 mvp_mat = proj_mat * modelview_mat;
+ CHECK_GL_ERROR();
+
+ graphics->EnableVertexAttribArray(shader_attrib_vert_coord);
+ CHECK_GL_ERROR();
+ graphics->EnableVertexAttribArray(shader_attrib_tex_coord);
+ CHECK_GL_ERROR();
+ static const GLfloat verts[] = {
+ 0, 0, 0, 0,
+ 1, 0, 1, 0,
+ 1, 1, 1, 1,
+ 0, 1, 0, 1
+ };
+ static const GLuint indices[] = {
+ 0, 1, 2,
+ 0, 2, 3
+ };
+ static VBOContainer vert_vbo;
+ static VBOContainer index_vbo;
+ static bool vbo_filled = false;
+ if(!vbo_filled) {
+ vert_vbo.Fill(kVBOFloat | kVBOStatic, sizeof(verts), (void*)verts);
+ index_vbo.Fill(kVBOElement | kVBOStatic, sizeof(indices), (void*)indices);
+ vbo_filled = true;
+ }
+ vert_vbo.Bind();
+ index_vbo.Bind();
+
+ glVertexAttribPointer(shader_attrib_vert_coord, 2, GL_FLOAT, false, 4*sizeof(GLfloat), 0);
+ CHECK_GL_ERROR();
+ glVertexAttribPointer(shader_attrib_tex_coord, 2, GL_FLOAT, false, 4*sizeof(GLfloat), (void*)(sizeof(GL_FLOAT)*2));
+ CHECK_GL_ERROR();
+
+ int num_indices = 6;
+
+ glUniformMatrix4fv(uniform_mvp_mat, 1, false, (GLfloat*)&mvp_mat);
+ CHECK_GL_ERROR();
+ vec4 color(1.0f);
+ glUniform4fv(uniform_color, 1, &color[0]);
+ CHECK_GL_ERROR();
+
+ bindTexture(id, 0);
+ CHECK_GL_ERROR();
+ graphics->DrawElements(GL_TRIANGLES, num_indices, GL_UNSIGNED_INT, 0);
+ CHECK_GL_ERROR();
+
+ graphics->BindElementVBO(0);
+ graphics->BindArrayVBO(0);
+ graphics->ResetVertexAttribArrays();
+ CHECK_GL_ERROR();
+}
+
+int Textures::AllocateFreeSlot() {
+ if(free_spaces.empty()) {
+ int id = (int)textures.size();
+ textures.resize(id+1);
+ textures[id].ref_count = 1;
+ textures[id].notify_on_dispose = false;
+ return id;
+ } else {
+ int which_space = free_spaces.back();
+ free_spaces.resize(free_spaces.size()-1);
+ LOG_ASSERT(textures[which_space].ref_count == 0);
+ LOG_ASSERT(textures[which_space].gl_texture_id == UNLOADED_ID);
+ LOG_ASSERT(textures[which_space].name == "Disposed");
+ textures[which_space].notify_on_dispose = false;
+ textures[which_space].ref_count = 1;
+ return which_space;
+ }
+}
+
+void Textures::DeleteUnusedTextures() {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ int num_deleted = 0;
+ for (unsigned i=0; i<textures.size(); i++) {
+ if( textures[i].ref_count <= 0 &&
+ (textures[i].gl_texture_id != UNLOADED_ID ||
+ !textures[i].sub_textures.empty()))
+ {
+ //printf("Deleting %s\n", textures[i].texture_name.c_str());
+ DisposeTexture(i);
+ ++num_deleted;
+ } else if(textures[i].ref_count > 0){
+ //printf("Keeping %s\n", textures[i].texture_name.c_str());
+ }
+ }
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+}
+
+unsigned int Textures::makeCubemapTextureInternal(int width, int height, GLint internal_format, GLint format, MipmapParam mipmap_param) {
+ CHECK_GL_ERROR();
+
+ unsigned which = AllocateFreeSlot();
+
+ Texture& texture = textures[which];
+ texture.Init();
+ texture.cube_map = true;
+ texture.width = width;
+ texture.height = height;
+ texture.tex_target = GL_TEXTURE_CUBE_MAP;
+ texture.internal_format = internal_format;
+ texture.format = format;
+
+ texture.use_srgb = (internal_format == GL_SRGB_ALPHA);
+
+ glGenTextures(1, &texture.gl_texture_id);
+ LOG_ASSERT(texture.gl_texture_id != INVALID_ID);
+ LOG_ASSERT(texture.gl_texture_id != UNLOADED_ID);
+
+ glBindTexture(texture.tex_target, texture.gl_texture_id);
+ texture.mag_filter = GL_LINEAR;
+ if(mipmap_param == NO_MIPMAPS){
+ texture.min_filter = GL_LINEAR;
+ } else {
+ texture.min_filter = GL_LINEAR_MIPMAP_LINEAR;
+ }
+
+ texture.wrap_s = GL_CLAMP_TO_EDGE;
+ texture.wrap_t = GL_CLAMP_TO_EDGE;
+
+ glTexParameteri(texture.tex_target, GL_TEXTURE_MIN_FILTER, texture.min_filter);
+ glTexParameteri(texture.tex_target, GL_TEXTURE_MAG_FILTER, texture.mag_filter);
+ glTexParameteri(texture.tex_target, GL_TEXTURE_WRAP_S, texture.wrap_s);
+ glTexParameteri(texture.tex_target, GL_TEXTURE_WRAP_T, texture.wrap_t);
+ glTexParameteri(texture.tex_target, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
+ int level = 0;
+ unsigned int totalTextureSize = 0;
+ while((width >= 1 && height >= 1) && (mipmap_param == MIPMAPS || level == 0)) {
+ for (int i=0; i<6; i++) {
+ if(internal_format == GL_RGBA16F) {
+ if(GLEW_ARB_texture_float) {
+ glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, level, internal_format, width, height, 0, format, GL_FLOAT, NULL);
+ totalTextureSize += width*height*8;
+ } else {
+ glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, level, GL_RGBA, width, height, 0, format, GL_UNSIGNED_BYTE, NULL);
+ totalTextureSize += width*height*4;
+ }
+ } else {
+ glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, level, internal_format, width, height, 0, format, GL_UNSIGNED_BYTE, NULL);
+ totalTextureSize += width*height*4;
+ }
+ }
+ width /= 2;
+ height /= 2;
+ ++level;
+ }
+ CHECK_GL_ERROR();
+ return which;
+}
+
+TextureRef Textures::makeCubemapTexture(int width, int height, GLint internal_format, GLint format, MipmapParam mipmap_param) {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ unsigned int t = makeCubemapTextureInternal(width, height, internal_format, format, mipmap_param);
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ TextureRef tex_ref(t);
+ DecrementRefCount(t);
+ return tex_ref;
+}
+
+unsigned int Textures::makeTextureInternal(const TextureData &texture_data) {
+ CHECK_GL_ERROR();
+ unsigned which = AllocateFreeSlot();
+
+ TextureData* texture_data_ptr = new TextureData(texture_data);
+ textures[which].Init();
+ textures[which].sub_textures.resize(1);
+ textures[which].sub_textures[0].texture_data = texture_data_ptr;
+ textures[which].width = texture_data.GetWidth();
+ textures[which].height = texture_data.GetHeight();
+
+ textures[which].wrap_s = m_wrap_s;
+ textures[which].wrap_t = m_wrap_t;
+ textures[which].min_filter = min_filter;
+ textures[which].mag_filter = mag_filter;
+
+ textures[which].no_mipmap = true;
+
+ textures[which].internal_format = texture_data.GetGLInternalFormat();
+ textures[which].format = texture_data.GetGLBaseFormat();
+ textures[which].tex_target = GL_TEXTURE_2D;
+
+ TextureToVRAM(which);
+
+ return which;
+}
+
+TextureRef Textures::makeTexture(const TextureData &texture_data) {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ unsigned int t = makeTextureInternal(texture_data);
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ TextureRef tex_ref(t);
+ DecrementRefCount(t);
+ return tex_ref;
+}
+
+unsigned int Textures::makeTextureInternal(int width, int height, GLint internal_format, GLint format, bool mipmap, void* data) {
+ PROFILER_ZONE(g_profiler_ctx, "Textures::makeTextureInternal(int width, int height, GLint internal_format, GLint format, bool mipmap, void* data)");
+ CHECK_GL_ERROR();
+ unsigned which = AllocateFreeSlot();
+
+ Texture& texture = textures[which];
+ texture.Init();
+ texture.sub_textures.resize(1);
+ texture.width = width;
+ texture.height = height;
+ texture.internal_format = internal_format;
+ texture.format = format;
+ texture.tex_target = GL_TEXTURE_2D;
+
+ glGenTextures(1, &texture.gl_texture_id);
+ LOG_ASSERT(texture.gl_texture_id != INVALID_ID);
+ LOG_ASSERT(texture.gl_texture_id != UNLOADED_ID);
+
+ CHECK_GL_ERROR();
+ glBindTexture(texture.tex_target, texture.gl_texture_id);
+ if(internal_format == GL_RGBA16F) {
+ if(GLEW_ARB_texture_float) {
+ glTexImage2D(texture.tex_target, 0, internal_format, width, height, 0,
+ format, GL_FLOAT, data);
+ } else {
+ glTexImage2D(texture.tex_target, 0, GL_RGBA, width, height, 0,
+ format, GL_UNSIGNED_BYTE, data);
+ }
+ } else if (IsCompressedGLFormat(internal_format)) {
+ int data_size = ((width + 3) / 4) * ((height + 3) / 4) * 16;
+ std::vector<char> zeros(data_size, 0);
+ PROFILER_ZONE(g_profiler_ctx, "glCompressedTexImage2D");
+ int temp_width = width;
+ int temp_height = height;
+ int temp_level = 0;
+ glCompressedTexImage2D(texture.tex_target, temp_level, internal_format, temp_width, temp_height, 0, data_size, &zeros[0]);
+ while(mipmap && (temp_width > 1 || temp_height > 1)) {
+ ++temp_level;
+ if(temp_width > 1){
+ temp_width /= 2;
+ }
+ if(temp_height > 1){
+ temp_height /= 2;
+ }
+ data_size = ((temp_width + 3) / 4) * ((temp_height + 3) / 4) * 16;
+ glCompressedTexImage2D(texture.tex_target, temp_level, internal_format, temp_width, temp_height, 0, data_size, &zeros[0]);
+ }
+ } else {
+ glTexImage2D(texture.tex_target, 0, internal_format, width, height, 0,
+ format, GL_UNSIGNED_BYTE, data);
+ }
+ CHECK_GL_ERROR();
+ if (mipmap) texture.min_filter = GL_LINEAR_MIPMAP_LINEAR;
+ else texture.min_filter = GL_LINEAR;
+ texture.mag_filter = GL_LINEAR;
+ texture.wrap_s = GL_CLAMP_TO_EDGE;
+ texture.wrap_t = GL_CLAMP_TO_EDGE;
+ glTexParameteri(texture.tex_target, GL_TEXTURE_WRAP_S, texture.wrap_s);
+ glTexParameteri(texture.tex_target, GL_TEXTURE_WRAP_T, texture.wrap_t);
+ glTexParameteri(texture.tex_target, GL_TEXTURE_MIN_FILTER, texture.min_filter);
+ glTexParameteri(texture.tex_target, GL_TEXTURE_MAG_FILTER, texture.mag_filter);
+ if(Graphics::Instance()->config_.anisotropy()) {
+ glTexParameterf(texture.tex_target, GL_TEXTURE_MAX_ANISOTROPY_EXT, Graphics::Instance()->config_.anisotropy());
+ }
+ CHECK_GL_ERROR();
+ if(mipmap && !IsCompressedGLFormat(internal_format)) {
+ PROFILER_ZONE(g_profiler_ctx, "glGenerateMipmap");
+ glGenerateMipmap(texture.tex_target);
+ }
+ CHECK_GL_ERROR();
+
+ return which;
+}
+
+unsigned int Textures::make3DTextureInternal(int* dims, GLint internal_format, GLint format, bool mipmap, void* data) {
+ CHECK_GL_ERROR();
+ unsigned which = AllocateFreeSlot();
+
+ Texture& texture = textures[which];
+ texture.Init();
+ texture.width = dims[0];
+ texture.height = dims[1];
+ texture.depth = dims[2];
+ texture.internal_format = internal_format;
+ texture.format = format;
+ texture.tex_target = GL_TEXTURE_3D;
+
+ glGenTextures(1, &texture.gl_texture_id);
+ LOG_ASSERT(texture.gl_texture_id != INVALID_ID);
+ LOG_ASSERT(texture.gl_texture_id != UNLOADED_ID);
+
+ CHECK_GL_ERROR();
+ glBindTexture(texture.tex_target, texture.gl_texture_id);
+ if(internal_format == GL_RGBA16F) {
+ glTexImage3D(texture.tex_target, 0, internal_format, dims[0], dims[1], dims[2], 0, format, GL_FLOAT, data);
+ } else {
+ glTexImage3D(texture.tex_target, 0, internal_format, dims[0], dims[1], dims[2], 0, format, GL_UNSIGNED_BYTE, data);
+ }
+ CHECK_GL_ERROR();
+ if (mipmap) glTexParameteri(texture.tex_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+ else glTexParameteri(texture.tex_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(texture.tex_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(texture.tex_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(texture.tex_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexParameteri(texture.tex_target, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
+ if(Graphics::Instance()->config_.anisotropy()) {
+ glTexParameterf(texture.tex_target, GL_TEXTURE_MAX_ANISOTROPY_EXT, Graphics::Instance()->config_.anisotropy());
+ }
+ CHECK_GL_ERROR();
+ if(mipmap) {
+ PROFILER_ZONE(g_profiler_ctx, "glGenerateMipmap");
+ glGenerateMipmap(texture.tex_target);
+ }
+ CHECK_GL_ERROR();
+
+ return which;
+}
+
+TextureRef Textures::makeTexture(int width, int height, GLint internal_format, GLint format, bool mipmap, void* data) {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ unsigned int t = makeTextureInternal(width, height, internal_format, format, mipmap, data);
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ TextureRef tex_ref(t);
+ DecrementRefCount(t);
+ return tex_ref;
+}
+
+TextureRef Textures::make3DTexture(int width, int height, int depth, GLint internal_format, GLint format, bool mipmap, void* data) {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ int dims[3] = {width, height, depth};
+ unsigned int t = make3DTextureInternal(dims, internal_format, format, mipmap, data);
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ TextureRef tex_ref(t);
+ DecrementRefCount(t);
+ return tex_ref;
+}
+
+unsigned int Textures::makeTextureColorInternal(int width, int height, GLint internal_format, GLint format, float red, float green, float blue, float alpha, bool mipmap) {
+ CHECK_GL_ERROR();
+ unsigned which = AllocateFreeSlot();
+
+ Texture& texture = textures[which];
+ texture.Init();
+ texture.width = width;
+ texture.height = height;
+ texture.internal_format = internal_format;
+ texture.format = format;
+ texture.no_mipmap = !mipmap;
+ texture.tex_target = GL_TEXTURE_2D;
+
+ std::vector<GLubyte> data(width*height*4);
+
+ unsigned char red_byte = (unsigned char)(red*255);
+ unsigned char green_byte = (unsigned char)(green*255);
+ unsigned char blue_byte = (unsigned char)(blue*255);
+ unsigned char alpha_byte = (unsigned char)(alpha*255);
+
+ int index;
+ for (int j = 0; j < width; j++) {
+ for (int i = 0; i < height; i++) {
+ index = (i+j*height)*4;
+ data[index+0] = red_byte;
+ data[index+1] = green_byte;
+ data[index+2] = blue_byte;
+ data[index+3] = alpha_byte;
+ }
+ }
+
+ glGenTextures(1, &textures[which].gl_texture_id);
+ LOG_ASSERT(textures[which].gl_texture_id != INVALID_ID);
+ LOG_ASSERT(textures[which].gl_texture_id != UNLOADED_ID);
+
+ glBindTexture(texture.tex_target, texture.gl_texture_id);
+
+ unsigned int totalTextureSize = 0;
+ if(internal_format == GL_RGBA16F) {
+ if(GLEW_ARB_texture_float) {
+ glTexImage2D(texture.tex_target, 0, internal_format, width, height, 0,
+ format, GL_FLOAT, &data[0]);
+ totalTextureSize += width*height*8;
+ } else {
+ glTexImage2D(texture.tex_target, 0, GL_RGBA, width, height, 0,
+ format, GL_UNSIGNED_BYTE, &data[0]);
+ totalTextureSize += width*height*4;
+ }
+ } else {
+ glTexImage2D(texture.tex_target, 0, internal_format, width, height, 0,
+ format, GL_UNSIGNED_BYTE, &data[0]);
+ totalTextureSize += width*height*4;
+ }
+ if (mipmap) glTexParameteri(texture.tex_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+ else glTexParameteri(texture.tex_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(texture.tex_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(texture.tex_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(texture.tex_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ if(mipmap) {
+ glGenerateMipmap(texture.tex_target);
+ totalTextureSize *= 4;
+ totalTextureSize /= 3;
+ }
+
+ CHECK_GL_ERROR();
+
+ RebindTexture(GetActiveTexture());
+
+ return which;
+}
+
+TextureRef Textures::makeTextureColor(int width, int height, GLint internal_format, GLint format, float red, float green, float blue, float alpha, bool mipmap) {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ unsigned int t = makeTextureColorInternal(width, height, internal_format, format, red, green, blue, alpha, mipmap);
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ TextureRef tex_ref(t);
+ DecrementRefCount(t);
+ return tex_ref;
+}
+
+unsigned int Textures::makeTextureTestPatternInternal(int width, int height, GLint internal_format, GLint format, bool mipmap) {
+ CHECK_GL_ERROR();
+ unsigned which = AllocateFreeSlot();
+
+ Texture& texture = textures[which];
+ texture.Init();
+ texture.width = width;
+ texture.height = height;
+ texture.internal_format = internal_format;
+ texture.format = format;
+ texture.tex_target = GL_TEXTURE_2D;
+
+ std::vector<GLubyte> data(width*height*4);
+
+ int index;
+ int multiple = width/32;
+ int cornerWidth = multiple*4;
+ for (int j = 0; j < width; j++) {
+ for (int i = 0; i < height; i++) {
+ index = (i+j*width)*4;
+ //Color the corners
+ if( (i < cornerWidth && j < cornerWidth ) ||
+ (i > height-cornerWidth && j > height-cornerWidth) ||
+ (i > height-cornerWidth && j < cornerWidth ) ||
+ (i < cornerWidth && j > height-cornerWidth) )
+ {
+ data[index+0] = 255;
+ }
+ else
+ {
+ data[index+0] = ((i/multiple+j/multiple)%2)*255;
+ }
+
+ data[index+1] = data[index];
+ data[index+2] = data[index];
+ data[index+3] = 128;
+ }
+ }
+
+ glGenTextures(1, &texture.gl_texture_id);
+ LOG_ASSERT(texture.gl_texture_id != INVALID_ID);
+ LOG_ASSERT(texture.gl_texture_id != UNLOADED_ID);
+
+ glBindTexture(texture.tex_target, texture.gl_texture_id);
+ if(internal_format == GL_RGBA16F) {
+ if(GLEW_ARB_texture_float) {
+ glTexImage2D(texture.tex_target, 0, internal_format, width, height, 0,
+ format, GL_FLOAT, &data[0]);
+ } else {
+ glTexImage2D(texture.tex_target, 0, GL_RGBA, width, height, 0,
+ format, GL_UNSIGNED_BYTE, &data[0]);
+ }
+ } else {
+ glTexImage2D(texture.tex_target, 0, internal_format, width, height, 0,
+ format, GL_UNSIGNED_BYTE, &data[0]);
+ }
+ if (mipmap) glTexParameteri(texture.tex_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+ else glTexParameteri(texture.tex_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(texture.tex_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(texture.tex_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(texture.tex_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ if(mipmap) {
+ glGenerateMipmap(texture.tex_target);
+ }//GLuint
+ CHECK_GL_ERROR();
+
+ RebindTexture(GetActiveTexture());
+
+ return which;
+}
+
+TextureRef Textures::makeTextureTestPattern(int width, int height, GLint internal_format, GLint format, bool mipmap) {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ unsigned int t = makeTextureTestPatternInternal(width, height, internal_format, format, mipmap);
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ TextureRef tex_ref(t);
+ DecrementRefCount(t);
+ return tex_ref;
+}
+
+
+unsigned int Textures::makeArrayTextureInternal(unsigned int num_slices, unsigned char flags) {
+ unsigned which = AllocateFreeSlot();
+
+ Texture& texture = textures[which];
+ texture.Init();
+ texture.num_slices = 0;
+ texture.sub_textures.reserve(num_slices);
+ texture.use_srgb = (flags & PX_SRGB) != 0;
+ texture.no_mipmap = (flags & PX_NOMIPMAP)!=0;
+ texture.no_reduce = (flags & PX_NOREDUCE)!=0;
+ texture.no_live_update = (flags & PX_NOLIVEUPDATE) != 0;;
+
+ texture.wrap_s = m_wrap_s;
+ texture.wrap_t = m_wrap_t;
+ texture.min_filter = min_filter;
+ texture.mag_filter = mag_filter;
+ texture.tex_target = GL_TEXTURE_2D_ARRAY;
+
+ return which;
+}
+
+
+unsigned int Textures::makeCubemapArrayTextureInternal(int num_slices, int width, int height, GLint internal_format, GLint format, unsigned char flags) {
+ unsigned which = AllocateFreeSlot();
+
+ Texture& texture = textures[which];
+ texture.Init();
+ texture.num_slices = num_slices;
+ texture.cube_map = true;
+ texture.width = width;
+ texture.height = height;
+ texture.tex_target = GL_TEXTURE_CUBE_MAP_ARRAY_ARB;
+
+ texture.use_srgb = (flags & PX_SRGB) != 0;
+ texture.no_mipmap = (flags & PX_NOMIPMAP)!=0;
+ texture.no_reduce = (flags & PX_NOREDUCE)!=0;
+
+ CHECK_GL_ERROR();
+ glGenTextures(1, &texture.gl_texture_id);
+ LOG_ASSERT(texture.gl_texture_id != INVALID_ID);
+ LOG_ASSERT(texture.gl_texture_id != UNLOADED_ID);
+
+ CHECK_GL_ERROR();
+ glBindTexture(texture.tex_target, texture.gl_texture_id);
+ if(texture.no_mipmap){
+ glTexParameteri(texture.tex_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ } else {
+ glTexParameteri(texture.tex_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+ }
+ glTexParameteri(texture.tex_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(texture.tex_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(texture.tex_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexParameteri(texture.tex_target, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
+ CHECK_GL_ERROR();
+ int level = 0;
+ std::vector<float> pixels(width*height*num_slices*6*4);
+ for(int i=0, len=pixels.size(); i<len; i+=16){
+ pixels[i+0] = 1.0f;
+ pixels[i+1] = 0.0f;
+ pixels[i+2] = 0.0f;
+ pixels[i+3] = 0.0f;
+
+ pixels[i+4] = 0.0f;
+ pixels[i+5] = 1.0f;
+ pixels[i+6] = 0.0f;
+ pixels[i+7] = 0.0f;
+
+ pixels[i+8] = 0.0f;
+ pixels[i+9] = 0.0f;
+ pixels[i+10] = 1.0f;
+ pixels[i+11] = 0.0f;
+
+ pixels[i+12] = 1.0f;
+ pixels[i+13] = 1.0f;
+ pixels[i+14] = 1.0f;
+ pixels[i+15] = 1.0f;
+ }
+ CHECK_GL_ERROR();
+ glTexImage3D(GL_TEXTURE_CUBE_MAP_ARRAY_ARB, level, internal_format, width, height, num_slices*6, 0, format, GL_FLOAT, &pixels[0]);
+ CHECK_GL_ERROR();
+ glGenerateMipmap(GL_TEXTURE_CUBE_MAP_ARRAY_ARB);
+ CHECK_GL_ERROR();
+ return which;
+}
+
+
+unsigned int Textures::makeFlatCubemapArrayTextureInternal(int num_slices, int width, int height, GLint internal_format, GLint format, unsigned char flags) {
+ unsigned which = AllocateFreeSlot();
+
+ Texture& texture = textures[which];
+ texture.Init();
+ texture.num_slices = num_slices;
+ texture.width = width*6;
+ texture.height = height;
+ texture.tex_target = GL_TEXTURE_2D_ARRAY;
+
+ texture.format = format;
+ texture.internal_format = internal_format;
+
+ texture.use_srgb = (flags & PX_SRGB) != 0;
+ texture.no_mipmap = (flags & PX_NOMIPMAP)!=0;
+ texture.no_reduce = (flags & PX_NOREDUCE)!=0;
+
+ CHECK_GL_ERROR();
+ glGenTextures(1, &texture.gl_texture_id);
+ LOG_ASSERT(texture.gl_texture_id != INVALID_ID);
+ LOG_ASSERT(texture.gl_texture_id != UNLOADED_ID);
+
+ CHECK_GL_ERROR();
+ glBindTexture(texture.tex_target, texture.gl_texture_id);
+ texture.wrap_s = m_wrap_s;
+ texture.wrap_t = m_wrap_t;
+ texture.min_filter = min_filter;
+ texture.mag_filter = mag_filter;
+ glTexParameteri(texture.tex_target, GL_TEXTURE_MIN_FILTER, texture.min_filter);
+ glTexParameteri(texture.tex_target, GL_TEXTURE_MAG_FILTER, texture.mag_filter);
+ glTexParameteri(texture.tex_target, GL_TEXTURE_WRAP_S, texture.wrap_s);
+ glTexParameteri(texture.tex_target, GL_TEXTURE_WRAP_T, texture.wrap_t);
+ CHECK_GL_ERROR();
+ std::vector<float> pixels(width*height*num_slices*6*4, 0.0f);
+ glTexImage3D(texture.tex_target, 0, internal_format, width*6, height, num_slices, 0, format, GL_FLOAT, &pixels[0]);
+ glGenerateMipmap(GL_TEXTURE_2D_ARRAY);
+ CHECK_GL_ERROR();
+
+ RebindTexture(GetActiveTexture());
+ return which;
+}
+
+TextureRef Textures::getDetailColorArray() {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ if (!detail_color_texture_ref.valid()) {
+ setWrap(GL_REPEAT, GL_REPEAT);
+ unsigned int t = makeArrayTextureInternal(0, PX_SRGB);
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ detail_color_texture_ref = TextureRef(t);
+ DecrementRefCount(t);
+ SetTextureName(detail_color_texture_ref, "Detail Texture Array - Color");
+ } else {
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ }
+
+ return detail_color_texture_ref;
+}
+
+
+TextureRef Textures::getDetailNormalArray() {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ if (!detail_normal_texture_ref.valid()) {
+ setWrap(GL_REPEAT, GL_REPEAT);
+ unsigned int t = makeArrayTextureInternal(0, 0);
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ detail_normal_texture_ref = TextureRef(t);
+ DecrementRefCount(t);
+ SetTextureName(detail_normal_texture_ref, "Detail Texture Array - Normals");
+ } else {
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ }
+
+ return detail_normal_texture_ref;
+}
+
+TextureRef Textures::makeArrayTexture(unsigned int num_slices, unsigned char flags) {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ unsigned int t = makeArrayTextureInternal(num_slices, flags);
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ TextureRef tex_ref(t);
+ DecrementRefCount(t);
+ return tex_ref;
+}
+
+TextureRef Textures::makeCubemapArrayTexture(int num_slices, int width, int height, GLint internal_format, GLint format, unsigned char flags) {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ unsigned int t = makeCubemapArrayTextureInternal(num_slices, width, height, internal_format, format, flags);
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ TextureRef tex_ref(t);
+ DecrementRefCount(t);
+ return tex_ref;
+}
+
+TextureRef Textures::makeFlatCubemapArrayTexture(int num_slices, int width, int height, GLint internal_format, GLint format, unsigned char flags) {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ unsigned int t = makeFlatCubemapArrayTextureInternal(num_slices, width, height, internal_format, format, flags);
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ TextureRef tex_ref(t);
+ DecrementRefCount(t);
+ return tex_ref;
+}
+
+
+bool IsCompressedFile(char* path) {
+ int end = strlen(path) - 1;
+ if (end >= 3 &&
+ path[end - 3] == '.'
+ && (
+ (path[end - 2] == 'd' &&
+ path[end - 1] == 'd' &&
+ path[end - 0] == 's')
+ ||
+ (path[end - 2] == 'c' &&
+ path[end - 1] == 'r' &&
+ path[end - 0] == 'n')
+ ))
+ {
+ return true;
+ }
+ return false;
+}
+
+int Textures::loadTexture(const std::string& rel_path, unsigned int which, unsigned char flags) {
+ static const int kBufSize = 256;
+ char buf[kBufSize];
+ FormatString(buf, kBufSize, "loadTexture: %s", rel_path.c_str());
+ PROFILER_ZONE_DYNAMIC_STRING(g_profiler_ctx, buf);
+
+ // Extract flag bools from bitfield
+ //bool no_convert = (flags & PX_NOCONVERT)!=0;
+ bool no_mipmap = (flags & PX_NOMIPMAP)!=0;
+ bool use_srgb = (flags & PX_SRGB)!=0;
+ bool no_live_update = (flags & PX_NOLIVEUPDATE)!=0;
+ bool no_reduce = (flags & PX_NOREDUCE)!=0;
+ bool no_convert = (flags & PX_NOCONVERT)!=0;
+
+ if(config["no_texture_convert"].toBool() == true){
+ no_convert = true;
+ }
+
+ // Get absolute path to texture (or compressed equivalent)
+ char abs_path[kPathSize];
+ PathFlags res_source;
+ ModID modsource;
+ int err = FindImagePath(rel_path.c_str(), abs_path, kPathSize, kDataPaths | kModPaths | kWriteDir | kAbsPath | kModWriteDirs, true, &res_source, true, &modsource );
+ if(err == -1){
+ //DisplayError("Error", "Could not find texture: %s", rel_path.c_str());
+ return kLoadErrorMissingFile;
+ }
+
+ // Check if dds file
+ bool dds_suffix = IsCompressedFile(abs_path);
+
+ if(!dds_suffix && !no_convert && err == 0 && res_source != kAbsPath){
+ std::string dst_path = AssemblePath(GetWritePath(modsource), SanitizePath(rel_path + convert_file_type));
+ if(process_pool->NumProcesses() > 0){
+ std::ostringstream oss;
+ std::string src_complete = abs_path;
+ GetCompletePath(&src_complete);
+ std::string dst_complete = dst_path;
+ GetCompletePath(&dst_complete);
+ std::string tmp_complete = GetTempDDSPath(dst_path,true,conversion_num);
+ GetCompletePath(&tmp_complete);
+ oss << "ConvertTexture \"" << src_complete << "\" \"" << dst_complete <<
+ "\" \"" << tmp_complete << "\"";
+ ++conversion_num;
+ process_pool->Schedule(oss.str());
+ } else {
+ ConvertImage(abs_path, dst_path, GetTempDDSPath(dst_path,true,conversion_num), TextureData::Fast);
+ }
+
+ strncpy(abs_path, dst_path.c_str(), kPathSize-1);
+ }
+
+ if(which>=textures.size()){
+ textures.resize(which+1);
+ }
+ Texture& tex = textures[which];
+
+ tex.Init();
+ tex.flags = flags;
+ tex.sub_textures.resize(1);
+ tex.sub_textures[0].texture_name = rel_path;
+ tex.sub_textures[0].load_name = abs_path;
+ tex.sub_textures[0].modsource = modsource;
+ tex.no_mipmap = no_mipmap;
+ tex.no_reduce = no_reduce;
+ tex.no_live_update = no_live_update;
+ tex.sub_textures[0].orig_modified = GetDateModifiedInt64(abs_path);
+ tex.sub_textures[0].texture_data = new TextureData();
+
+ // Try loading as .crn
+ if (dds_suffix) {
+ std::string crn_path(abs_path);
+ unsigned int crnlength = crn_path.length();
+ crn_path[crnlength - 3] = 'c';
+ crn_path[crnlength - 2] = 'r';
+ crn_path[crnlength - 1] = 'n';
+ if (FileExists(crn_path.c_str(), kDataPaths | kModPaths)) {
+ PROFILER_ZONE(g_profiler_ctx, "Loading crunch texture");
+ if(!tex.sub_textures[0].texture_data->Load(crn_path.c_str())) {
+ //DisplayError("Error", "Failed to load texture: %s", crn_path.c_str());
+ return kLoadErrorMissingFile;
+ }
+ } else {
+ PROFILER_ZONE(g_profiler_ctx, "Loading dds texture");
+ if (!tex.sub_textures[0].texture_data->Load(abs_path)) {
+ //DisplayError("Error", "Failed to load texture: %s", abs_path);
+ return kLoadErrorMissingFile;
+ }
+
+ ConvertToDXT5IfNeeded(tex.sub_textures[0], modsource);
+ }
+ } else {
+ PROFILER_ZONE(g_profiler_ctx, "Loading uncompressed texture data");
+ tex.sub_textures[0].texture_data->Load(abs_path);
+ }
+
+ tex.width = tex.sub_textures[0].texture_data->GetWidth();
+ tex.height = tex.sub_textures[0].texture_data->GetHeight();
+ if(/*Graphics::Instance()->config_.texture_reduce() && */!tex.no_reduce) {
+ //int extra_reduce = 1;
+ //if(tex.flags & PX_NOCONVERT){
+ // extra_reduce = 0;
+ //}
+ }
+ // TODO: generate mipmaps?
+
+ // Check if width and height are powers of two
+ bool pot = IsPow2(tex.width) && IsPow2(tex.height);
+
+ if(!pot && !(tex.no_reduce && tex.no_mipmap)){
+ std::ostringstream oss;
+ oss << "Dimensions of " << rel_path << " are " << tex.width
+ << " x " << tex.height << ", should be powers of two.";
+ DisplayError("Warning",oss.str().c_str());
+ }
+
+ tex.wrap_s = m_wrap_s;
+ tex.wrap_t = m_wrap_t;
+ tex.min_filter = min_filter;
+ tex.mag_filter = mag_filter;
+
+ tex.use_srgb = use_srgb;
+ return kLoadOk;
+}
+
+
+void Textures::loadArraySlice(const TextureRef& texref, unsigned int slice, const std::string& rel_path) {
+ PROFILED_TEXTURE_MUTEX_LOCK
+
+ LOG_ASSERT(texref.valid());
+
+ Texture &tex = textures[texref.id];
+ if (tex.gl_texture_id != UNLOADED_ID) {
+ tex.width = 0;
+ tex.height = 0;
+ // adding new slices, need to reupload texture to gl
+ // can't free old texture right here, we're not on main thread
+ LOGI << "Deferring delete of texture " << tex.gl_texture_id << std::endl;
+ deferred_delete_textures.push_back(tex.gl_texture_id);
+ tex.gl_texture_id = UNLOADED_ID;
+ }
+
+ // cube map arrays not supported yet, maybe not ever since it's GL 4.0 feature
+ LOG_ASSERT(!tex.cube_map);
+
+ char abs_path[kPathSize];
+ ModID modsource;
+ int err = FindImagePath(rel_path.c_str(), abs_path, kPathSize, kDataPaths | kModPaths | kWriteDir | kModWriteDirs, true, NULL, true, &modsource);
+ if(err == -1){
+ FatalError("Error", "Could not find texture: %s", rel_path.c_str());
+ }
+
+ bool dds_suffix = IsCompressedFile(abs_path);
+
+ std::string dst_path = std::string(abs_path);
+
+ if(!dds_suffix && err == 0){
+ dst_path = AssemblePath(GetWritePath(modsource), SanitizePath(rel_path + convert_file_type));
+ if(process_pool->NumProcesses() > 0){
+ std::ostringstream oss;
+ std::string src_complete = abs_path;
+ GetCompletePath(&src_complete);
+ std::string dst_complete = dst_path;
+ GetCompletePath(&dst_complete);
+ std::string tmp_complete = GetTempDDSPath(dst_path,true,conversion_num);
+ GetCompletePath(&tmp_complete);
+ oss << "ConvertTexture \"" << src_complete << "\" \"" << dst_complete <<
+ "\" \"" << tmp_complete << "\"";
+ ++conversion_num;
+ process_pool->Schedule(oss.str());
+ } else {
+ ConvertImage(abs_path, dst_path, GetTempDDSPath(dst_path,true,conversion_num), TextureData::Fast);
+ dds_suffix = true;
+ }
+ strncpy(abs_path, dst_path.c_str(), kPathSize-1);
+ }
+
+ if (slice >= tex.num_slices) {
+ tex.num_slices++;
+ tex.sub_textures.resize(tex.num_slices);
+ LOG_ASSERT(slice < tex.num_slices);
+ }
+ tex.sub_textures[slice].texture_name = rel_path;
+ tex.sub_textures[slice].load_name = abs_path;
+ tex.sub_textures[slice].modsource = modsource;
+ tex.sub_textures[slice].orig_modified = GetDateModifiedInt64(abs_path);
+
+ const bool kPrintAllSliceLoads = false;
+
+ tex.sub_textures[slice].texture_data = new TextureData();
+
+ // Try loading as .crn
+ if (dds_suffix) {
+ std::string crn_path(dst_path);
+ unsigned int crnlength = crn_path.length();
+ crn_path[crnlength - 3] = 'c';
+ crn_path[crnlength - 2] = 'r';
+ crn_path[crnlength - 1] = 'n';
+ if (FileExists(crn_path.c_str(), kDataPaths | kModPaths)) {
+ if (!tex.sub_textures[slice].texture_data->Load(crn_path.c_str())) {
+ FatalError("Error", "Failed to load texture: %s", crn_path.c_str());
+ }
+ } else {
+ if (!tex.sub_textures[slice].texture_data->Load(dst_path.c_str())) {
+ FatalError("Error", "Failed to load texture: %s", dst_path.c_str());
+ }
+
+ ConvertToDXT5IfNeeded(tex.sub_textures[slice], modsource);
+ }
+ } else {
+ tex.sub_textures[slice].texture_data->Load(abs_path);
+ }
+
+ // TODO: generate mipmaps?
+ if(kPrintAllSliceLoads){
+ LOGI << "Loading texture array slice " << slice << " from " << abs_path
+ << " size " << tex.sub_textures[slice].texture_data->GetWidth()
+ << "x" << tex.sub_textures[slice].texture_data->GetHeight()
+ << std::endl;
+ }
+
+ // first slice we load determines if we're compressed
+ if (tex.num_slices == 1) {
+ LOG_ASSERT(tex.width == 0);
+ LOG_ASSERT(tex.height == 0);
+
+ int width = tex.sub_textures[slice].texture_data->GetWidth();
+ int height = tex.sub_textures[slice].texture_data->GetHeight();
+
+ // Check if width and height are powers of two
+ bool pot = IsPow2(width) && IsPow2(height);
+
+ if(!pot && !(tex.no_reduce && tex.no_mipmap)){
+ std::ostringstream oss;
+ oss << "Dimensions of " << rel_path << " are " << width
+ << " x " << height << ", should be powers of two.";
+ DisplayError("Error",oss.str().c_str());
+ }
+ }
+
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+}
+
+
+unsigned int Textures::loadArraySlice(const TextureRef& texref, const std::string& rel_path) {
+ unsigned int slice;
+
+ {
+ PROFILED_TEXTURE_MUTEX_LOCK
+
+ LOG_ASSERT(texref.valid());
+ Texture &tex = textures[texref.id];
+
+ // check if it's already loaded
+ bool first_slice = tex.sub_textures.empty();
+ // tex.compressed is only valid if we already have loaded something
+ // and if we're loading the first slice we can't reuse anything anyway
+ if (!first_slice) {
+ for (slice = 0; slice < tex.sub_textures.size(); slice++) {
+ if (rel_path == tex.sub_textures[slice].texture_name) {
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ return slice;
+ break;
+ }
+ }
+ } else {
+ slice = 0;
+ }
+
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ }
+
+ loadArraySlice(texref, slice, rel_path);
+
+ return slice;
+}
+
+
+unsigned int Textures::makeRectangularTextureInternal(int width, int height, GLint internal_format, GLint format) {
+ CHECK_GL_ERROR();
+ unsigned which = AllocateFreeSlot();
+
+ Texture& texture = textures[which];
+ texture.Init();
+ texture.width = width;
+ texture.height = height;
+ texture.internal_format = internal_format;
+ texture.format = format;
+ texture.tex_target = GL_TEXTURE_2D;
+
+ glGenTextures(1, &texture.gl_texture_id);
+ LOG_ASSERT(texture.gl_texture_id != INVALID_ID);
+ LOG_ASSERT(texture.gl_texture_id != UNLOADED_ID);
+
+ CHECK_GL_ERROR();
+ glBindTexture(texture.tex_target, texture.gl_texture_id);
+ CHECK_GL_ERROR();
+ unsigned int totalTextureSize = 0;
+ if(internal_format == GL_RGBA16F || internal_format == GL_RGBA32F || internal_format == GL_DEPTH_COMPONENT
+ || internal_format == GL_DEPTH_COMPONENT24) {
+ if(Graphics::Instance()->features().HDR_enable()) {
+ glTexImage2D(texture.tex_target, 0, internal_format, width, height, 0,
+ format, GL_UNSIGNED_BYTE, NULL);
+ totalTextureSize += width*height*8;
+ int level = 0;
+ while(width > 1 || height > 1){
+ width = max(1, width/2);
+ height = max(1, height/2);
+ ++level;
+ glTexImage2D(texture.tex_target, level, internal_format, width, height, 0,
+ format, GL_UNSIGNED_BYTE, NULL);
+ totalTextureSize += width*height*8;
+ }
+ CHECK_GL_ERROR();
+ } else {
+ glTexImage2D(texture.tex_target, 0, GL_RGBA, width, height, 0,
+ format, GL_UNSIGNED_BYTE, NULL);
+ totalTextureSize += width*height*4;
+ int level = 0;
+ while(width > 1 || height > 1){
+ width = max(1, width/2);
+ height = max(1, height/2);
+ ++level;
+ glTexImage2D(texture.tex_target, level, GL_RGBA, width, height, 0,
+ format, GL_UNSIGNED_BYTE, NULL);
+ totalTextureSize += width*height*4;
+ }
+ CHECK_GL_ERROR();
+ }
+ } else {
+ glTexImage2D(texture.tex_target, 0, internal_format, width, height, 0,
+ format, GL_UNSIGNED_BYTE, NULL);
+ totalTextureSize += width*height*4;
+ int level = 0;
+ while(width > 1 || height > 1){
+ width = max(1, width/2);
+ height = max(1, height/2);
+ ++level;
+ glTexImage2D(texture.tex_target, level, internal_format, width, height, 0,
+ format, GL_UNSIGNED_BYTE, NULL);
+ totalTextureSize += width*height*4;
+ }
+ CHECK_GL_ERROR();
+ }
+ CHECK_GL_ERROR();
+ glTexParameteri(texture.tex_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+ CHECK_GL_ERROR();
+ glTexParameteri(texture.tex_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ CHECK_GL_ERROR();
+ glTexParameteri(texture.tex_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ CHECK_GL_ERROR();
+ glTexParameteri(texture.tex_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ CHECK_GL_ERROR();
+
+ CHECK_GL_ERROR();
+ glTexParameteri(texture.tex_target, GL_TEXTURE_COMPARE_MODE, GL_NONE);
+
+ CHECK_GL_ERROR();
+
+ texture.wrap_s = GL_CLAMP_TO_EDGE;
+ texture.wrap_t = GL_CLAMP_TO_EDGE;
+ texture.mag_filter = GL_LINEAR;
+ texture.min_filter = GL_LINEAR_MIPMAP_LINEAR;
+
+ return which;
+}
+
+TextureRef Textures::makeRectangularTexture(int width, int height, GLint internal_format, GLint format) {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ unsigned int t = makeRectangularTextureInternal(width, height, internal_format, format);
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ TextureRef tex_ref(t);
+ DecrementRefCount(t);
+ return tex_ref;
+}
+
+void Textures::SetTextureName(const TextureRef& texture_ref, const char* name) {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ if( textures[texture_ref.id].notify_on_dispose ) {
+ LOGI << "Giving texture with gl texture id " << textures[texture_ref.id].gl_texture_id << " the name " << name << std::endl;
+ }
+
+ textures[texture_ref.id].name = name;
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+}
+
+unsigned int Textures::makeBufferTextureInternal(size_t size, GLint internal_format, const void* data) {
+ CHECK_GL_ERROR();
+ unsigned which = AllocateFreeSlot();
+
+ Texture& texture = textures[which];
+ texture.Init();
+ texture.width = 0;
+ texture.height = 0;
+ texture.size = size;
+ texture.internal_format = internal_format;
+ texture.tex_target = GL_TEXTURE_BUFFER;
+
+ glGenBuffers(1, &texture.gl_buffer_id);
+ CHECK_GL_ERROR();
+ glBindBuffer(GL_TEXTURE_BUFFER, texture.gl_buffer_id);
+ glBufferData(GL_TEXTURE_BUFFER, size, data, GL_DYNAMIC_DRAW);
+ CHECK_GL_ERROR();
+
+ glGenTextures(1, &texture.gl_texture_id);
+ LOG_ASSERT(texture.gl_texture_id != INVALID_ID);
+ LOG_ASSERT(texture.gl_texture_id != UNLOADED_ID);
+
+ CHECK_GL_ERROR();
+ glBindTexture(texture.tex_target, texture.gl_texture_id);
+ glTexBuffer(texture.tex_target, internal_format, texture.gl_buffer_id);
+
+ CHECK_GL_ERROR();
+
+ return which;
+}
+
+TextureRef Textures::makeBufferTexture(size_t size, GLint internal_format, const void* data) {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ unsigned int t = makeBufferTextureInternal(size, internal_format, data);
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ TextureRef tex_ref(t);
+ DecrementRefCount(t);
+ return tex_ref;
+}
+
+//Return the OpenGL id of a texture
+GLuint Textures::returnTexture(const TextureRef &which_texture) {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ GLuint ret = textures[which_texture.id].gl_texture_id;
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ return ret;
+}
+
+GLuint Textures::returnTexture(const TextureAssetRef &which_texture) {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ GLuint ret = textures[which_texture->id].gl_texture_id;
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ return ret;
+}
+
+int Textures::getWidth(const TextureRef &which) {
+ return textures[which.id].width;
+}
+
+int Textures::getHeight(const TextureRef &which) {
+ return textures[which.id].height;
+}
+
+int Textures::getWidth(const TextureAssetRef &which) {
+ return textures[which->id].width;
+}
+
+int Textures::getHeight(const TextureAssetRef &which) {
+ return textures[which->id].height;
+}
+
+/**
+ * @brief Returns the width multiplied with the texture reduction factor if the texture is reduced
+ */
+int Textures::getReducedWidth(const TextureRef &which){
+ return (int) (textures[which.id].width * (textures[which.id].no_reduce ? 1.0f : Graphics::Instance()->config_.texture_reduction_factor()));
+}
+
+/**
+ * @brief Returns the height multiplied with the texture reduction factor if the texture is reduced
+ */
+int Textures::getReducedHeight(const TextureRef &which) {
+ return (int) (textures[which.id].height * (textures[which.id].no_reduce ? 1.0f : Graphics::Instance()->config_.texture_reduction_factor()));
+}
+
+/**
+ * @brief Returns the width multiplied with the texture reduction factor if the texture is reduced
+ */
+int Textures::getReducedWidth(const TextureAssetRef &which){
+ return (int) (textures[which->id].width * (textures[which->id].no_reduce ? 1.0f : Graphics::Instance()->config_.texture_reduction_factor()));
+}
+
+/**
+ * @brief Returns the height multiplied with the texture reduction factor if the texture is reduced
+ */
+int Textures::getReducedHeight(const TextureAssetRef &which) {
+ return (int) (textures[which->id].height * (textures[which->id].no_reduce ? 1.0f : Graphics::Instance()->config_.texture_reduction_factor()));
+}
+
+void Textures::EnsureInVRAM(const TextureAssetRef &id) {
+ EnsureInVRAM(id->id);
+}
+
+void Textures::EnsureInVRAM(const TextureRef &id) {
+ EnsureInVRAM(id.id);
+}
+
+void Textures::EnsureInVRAM(int which_texture) {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ if(textures[which_texture].gl_texture_id == UNLOADED_ID) {
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ TextureToVRAM(which_texture);
+ } else {
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ }
+}
+
+void Textures::RebindTexture(int unit) {
+ if(bound_texture[unit] == INVALID_ID) {
+ return;
+ }
+ Texture &texture = textures[bound_texture[unit]];
+ // GL_INVALID_OPERATION raised on first level load without this check
+ if( texture.ref_count <= 0 ) {
+ LOGE << "Trying to rebind texture with no references " << texture.name << std::endl;
+ } else if (texture.gl_texture_id == INVALID_ID || texture.gl_texture_id == UNLOADED_ID) {
+ LOGE << "Trying to rebind invalid texture id." << std::endl;
+ } else {
+ glBindTexture(texture.tex_target, texture.gl_texture_id);
+ }
+}
+
+void Textures::SetActiveTexture(int which_unit) {
+ if(active_texture != which_unit){
+ glActiveTexture(GL_TEXTURE0+which_unit);
+ active_texture = which_unit;
+ }
+}
+
+int Textures::GetActiveTexture() {
+ return active_texture;
+}
+
+void Textures::bindTexture(const TextureAssetRef &which_texture_ref, int which_unit) {
+ bindTexture(which_texture_ref->GetTextureRef(), which_unit);
+}
+
+void Textures::bindTexture(const TextureRef &which_texture_ref, int which_unit) {
+ bindTexture(which_texture_ref.id, which_unit);
+}
+
+void Textures::bindTexture(const unsigned int &which_texture, int which_unit) {
+ CHECK_GL_ERROR();
+ if(which_texture == INVALID_ID) {
+ LOGE << "Binding invalid teture ID" << std::endl;
+ return;
+ }
+ CHECK_GL_ERROR();
+ //Only bind this texture if it is not already bound, to save on overhead
+ PROFILED_TEXTURE_MUTEX_LOCK
+
+ if( textures[which_texture].ref_count <= 0 ) {
+ LOGE << "Trying to rebind texture with no references " << textures[which_texture].name << std::endl;
+ }
+
+ bool load_to_vram = textures[which_texture].gl_texture_id == UNLOADED_ID;
+ if(load_to_vram) {
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ TextureToVRAM(which_texture);
+ PROFILED_TEXTURE_MUTEX_LOCK
+ }
+ if(textures[which_texture].gl_texture_id == UNLOADED_ID){
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ return;
+ }
+
+ SetActiveTexture(which_unit);
+
+ if (bound_texture[which_unit] != INVALID_ID) {
+ // if new texture has different target (2d, cube map) than previous
+ // clear the previous bind
+ GLenum old_tex_target = textures[bound_texture[which_unit]].tex_target;
+ if (textures[which_texture].tex_target != old_tex_target) {
+ if( old_tex_target != GL_NONE ) {
+ glBindTexture(old_tex_target, 0);
+ }
+ }
+ }
+
+ bound_texture[which_unit]=which_texture;
+ RebindTexture(which_unit);
+ Shaders* shaders = Shaders::Instance();
+ if(shaders->bound_program!=-1) {
+ shaders->SetUniformInt(shaders->GetTexUniform(which_unit), which_unit);
+ }
+
+ CHECK_GL_ERROR();
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ return;
+}
+
+void Textures::InvalidateBindCacheInternal() {
+ SetActiveTexture(1); // To force update in case active_texture this is out of sync too
+ SetActiveTexture(0);
+ for (unsigned int i=0; i<_texture_units; i++) {
+ if (bound_texture[i] != INVALID_ID) {
+ SetActiveTexture(i);
+ if (textures[bound_texture[i]].cube_map) {
+ glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
+ } else {
+ glBindTexture(GL_TEXTURE_2D, 0);
+ }
+ bound_texture[i] = INVALID_ID;
+ }
+ }
+}
+
+void Textures::InvalidateBindCache() {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ InvalidateBindCacheInternal();
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+}
+
+void Textures::bindBlankTexture(int which_unit) {
+ if(!blank_texture_ref.valid()) {
+ blank_texture_ref = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>("Data/Textures/diffuse.tga", PX_SRGB, 0x0);
+ }
+ bindTexture(blank_texture_ref->GetTextureRef(), which_unit);
+}
+
+void Textures::bindBlankNormalTexture(int which_unit) {
+ if(!blank_normal_texture_ref.valid()) {
+ blank_normal_texture_ref = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>("Data/Textures/diffusebump.tga");
+ }
+ bindTexture(blank_normal_texture_ref->GetTextureRef(), which_unit);
+}
+
+const TextureRef Textures::GetBlankTextureRef() {
+ if(!blank_texture_ref.valid()) {
+ blank_texture_ref = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>("Data/Textures/diffuse.tga", PX_SRGB, 0x0);
+ }
+ return blank_texture_ref->GetTextureRef();
+}
+
+const TextureAssetRef Textures::GetBlankTextureAssetRef() {
+ if(!blank_texture_ref.valid()) {
+ blank_texture_ref = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>("Data/Textures/diffuse.tga", PX_SRGB, 0x0);
+ }
+ return blank_texture_ref;
+}
+
+const TextureRef Textures::GetBlankNormalTextureRef() {
+ if(!blank_normal_texture_ref.valid()) {
+ blank_normal_texture_ref = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>("Data/Textures/diffusebump.tga");
+ }
+ return blank_normal_texture_ref->GetTextureRef();
+}
+
+const TextureAssetRef Textures::GetBlankNormalTextureAssetRef() {
+ if(!blank_normal_texture_ref.valid()) {
+ blank_normal_texture_ref = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>("Data/Textures/diffusebump.tga");
+ }
+ return blank_normal_texture_ref;
+}
+
+bool Textures::IsRenderable(const TextureRef& texture) {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ bool ret = true;
+ if (texture.valid()) {
+ const Texture &t = textures[texture.id];
+ if (t.sub_textures.size() != 1) {
+ ret = false; // array or empty texture
+ } else {
+ const TextureData* td = t.sub_textures[0].texture_data;
+ if (td != NULL) {
+ ret = false; // loaded from disk
+ }
+ }
+ } else {
+ DisplayError("Error", "Checking if an invalid texture is renderable");
+ ret = false;
+ }
+
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ return ret;
+}
+
+bool Textures::IsCompressed(const TextureAssetRef& texture) {
+ return IsCompressed(texture->GetTextureRef());
+}
+
+bool Textures::IsCompressed(const TextureRef& texture) {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ bool ret = false;
+ if(texture.valid()){
+ const Texture &t = textures[texture.id];
+ if (t.sub_textures.empty()) {
+ //DisplayError("Error","Checking if an empty texture is compressed");
+ ret = false;
+ } else {
+ const TextureData* td = t.sub_textures[0].texture_data;
+ if (td == NULL) {
+ // WHY does this happen?
+ //DisplayError("Error","Checking if an empty texture is compressed");
+ ret = false;
+ } else {
+ assert(td != NULL);
+ ret = td->IsCompressed();
+ }
+ }
+ } else {
+ DisplayError("Error","Checking if an invalid texture is compressed");
+ ret = false;
+ }
+
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ return ret;
+}
+
+bool Textures::ReloadAsCompressed(const TextureAssetRef& texref) {
+ return ReloadAsCompressed(texref->GetTextureRef());
+}
+
+bool Textures::ReloadAsCompressed(const TextureRef& texref) {
+ // takes an uncompressed texture
+ // saves it in compressed format
+ // and replaces the current one with that
+ LOG_ASSERT(texref.valid());
+
+ Texture &texture = textures[texref.id];
+
+ LOG_ASSERT(texture.sub_textures.size() == 1);
+ LOG_ASSERT(!texture.sub_textures[0].texture_data->IsCompressed());
+ std::string newName = texture.sub_textures[0].texture_name + "_converted.dds";
+ std::string newPath = AssemblePath(GetWritePath(texture.sub_textures[0].modsource), newName);
+
+ LOGI << "Compressing texture " << texture.sub_textures[0].texture_name << " saving to " << newPath << std::endl;
+
+ CreateParentDirs(newPath.c_str());
+
+ for (unsigned int i = 0; i < texture.sub_textures.size(); i++) {
+ TextureData *tex_data = texture.sub_textures[i].texture_data;
+ LOG_ASSERT(tex_data != NULL);
+ if (!tex_data->HasMipmaps()) {
+ tex_data->GenerateMipmaps();
+ }
+ LOG_ASSERT(tex_data->HasMipmaps());
+ tex_data->ConvertDXT(crnlib::PIXEL_FMT_DXT5, TextureData::Fast);
+ }
+
+ texture.sub_textures[0].texture_data->SaveDDS(newPath.c_str());
+
+ char abs_path[kPathSize];
+ ModID modsource;
+ if(FindImagePath(newName.c_str(), abs_path, kPathSize, kWriteDir | kModWriteDirs, true, NULL, true, &modsource) == -1) {
+ LOGE << "Texture not found after writing it" << std::endl;
+ return false;
+ }
+
+ RemoveTextureFromVRAM(texref.id);
+
+ texture.sub_textures[0].load_name = abs_path;
+ texture.sub_textures[0].modsource = modsource;
+
+ return true;
+}
+
+void Textures::SaveToPNG( int texture, std::string path ) {
+ GLint width, height;
+
+ glBindTexture(GL_TEXTURE_2D, texture);
+ glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width );
+ glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height );
+
+ void* data = OG_MALLOC( sizeof(GLuint) * width * height );
+ glGetTexImage( GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, data );
+
+ ImageExport::SavePNG(path.c_str(), (unsigned char*)data, width, height);
+
+ OG_FREE(data);
+}
+
+void Textures::ResetVRAM() {
+ for(unsigned int i=0;i<textures.size();i++){
+ RemoveTextureFromVRAM(i);
+ }
+}
+
+void Textures::RemoveTextureFromVRAM( int which ) {
+ if(textures[which].gl_texture_id != UNLOADED_ID && textures[which].gl_texture_id != INVALID_ID){
+ if(glIsTexture(textures[which].gl_texture_id)){
+ glDeleteTextures(1,&textures[which].gl_texture_id);
+ }
+ textures[which].gl_texture_id = UNLOADED_ID;
+ }
+
+ if(textures[which].gl_buffer_id != UNLOADED_ID && textures[which].gl_buffer_id != INVALID_ID){
+ if(glIsBuffer(textures[which].gl_buffer_id)) {
+ glDeleteBuffers(1,&textures[which].gl_buffer_id);
+ }
+ textures[which].gl_buffer_id = UNLOADED_ID;
+ }
+}
+
+void Textures::ConvertToDXT5IfNeeded(SubTexture& texture, ModID modsource) const {
+ switch (texture.texture_data->GetGLInternalFormat()) {
+ case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
+ case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
+ case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
+ case GL_COMPRESSED_SRGB_S3TC_DXT1_EXT:
+ case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:
+ case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT:
+ {
+ std::string dst_path = AssemblePath(GetWritePath(modsource), SanitizePath(texture.texture_name + convert_file_type));
+ texture.texture_data->ConvertDXT(crnlib::PIXEL_FMT_DXT5, TextureData::Nice);
+
+ CreateParentDirs(dst_path);
+
+ texture.texture_data->SaveDDS(dst_path.c_str());
+
+ LOGW << texture.texture_name << " is not a DXT5 file, and will be converted" << std::endl;
+ }
+ }
+}
+
+Textures::~Textures() {
+ LOGI << "Waiting for process pool tasks to complete..." << std::endl;
+ process_pool->ClearQueuedTasks();
+ process_pool->WaitForTasksToComplete();
+ delete process_pool;
+}
+
+void Textures::SetProcessPoolsEnabled( bool val ) {
+ if(val){
+ LOGW << "Process pools feature disabled" << std::endl;
+ } else {
+ process_pool->Resize(0);
+ }
+}
+
+void Textures::ApplyAnisotropy() {
+ for (unsigned int i=0;i<textures.size();i++){
+ bindTexture(i,0);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, Graphics::Instance()->config_.anisotropy());
+ }
+}
+
+void Textures::updateBufferTexture(TextureRef& texture_ref, size_t size, const void* data) {
+ PROFILER_ZONE(g_profiler_ctx, "updateBufferTexture");
+ if (!texture_ref.valid()) {
+ return;
+ }
+ Texture &texture = textures[texture_ref.id];
+ LOG_ASSERT(texture.gl_texture_id != 0);
+ LOG_ASSERT(texture.gl_buffer_id != 0);
+ LOG_ASSERT(texture.tex_target == GL_TEXTURE_BUFFER);
+ glBindBuffer(GL_TEXTURE_BUFFER, texture.gl_buffer_id);
+ if (size > texture.size) {
+ PROFILER_ZONE(g_profiler_ctx, "Resize buffer");
+ // resize the buffer
+ glBufferData(GL_TEXTURE_BUFFER, size, NULL, GL_DYNAMIC_DRAW);
+ texture.size = size;
+ } else {
+ PROFILER_ZONE(g_profiler_ctx, "Discard? buffer");
+ glBufferData(GL_TEXTURE_BUFFER, texture.size, NULL, GL_DYNAMIC_DRAW);
+ glBindBuffer(GL_TEXTURE_BUFFER, texture.gl_buffer_id);
+ }
+ const bool kUseMapRange = false;
+ if(kUseMapRange) {
+ PROFILER_ZONE(g_profiler_ctx, "glMapBufferRange");
+ //Again, i don't believe that using unsynchronized here is necessarily safe, can we guarantee this?
+ //same as in vbocontainer
+ void* mapped = glMapBufferRange(GL_TEXTURE_BUFFER, 0, size, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT | GL_MAP_INVALIDATE_RANGE_BIT | GL_MAP_UNSYNCHRONIZED_BIT );
+ //void* mapped = glMapBufferRange(GL_TEXTURE_BUFFER, 0, size, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT | GL_MAP_INVALIDATE_RANGE_BIT );
+ CHECK_GL_ERROR();
+ {
+ PROFILER_ZONE(g_profiler_ctx, "memcpy");
+ memcpy(mapped, data, size);
+ }
+ glUnmapBuffer(GL_TEXTURE_BUFFER);
+ } else {
+ PROFILER_ZONE(g_profiler_ctx, "glBufferSubData");
+ glBufferSubData(GL_TEXTURE_BUFFER, 0, size, data);
+ }
+}
+