Files
noggit-red/src/noggit/TextureManager.cpp
2020-10-16 19:24:31 +03:00

431 lines
10 KiB
C++

// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#include <math/vector_2d.hpp>
#include <noggit/TextureManager.h>
#include <noggit/Log.h> // LogDebug
#include <opengl/context.hpp>
#include <opengl/scoped.hpp>
#include <opengl/shader.hpp>
#include <QtCore/QString>
#include <QtGui/QOffscreenSurface>
#include <QtGui/QOpenGLFramebufferObjectFormat>
#include <QtGui/QPixmap>
#include <QtOpenGL/QGLPixelBuffer>
#include <algorithm>
decltype (TextureManager::_) TextureManager::_;
void TextureManager::report()
{
std::string output = "Still in the Texture manager:\n";
_.apply ( [&] (std::string const& key, blp_texture const&)
{
output += " - " + key + "\n";
}
);
LogDebug << output;
}
#include <cstdint>
//! \todo Cross-platform syntax for packed structs.
#pragma pack(push,1)
struct BLPHeader
{
int32_t magix;
int32_t version;
uint8_t attr_0_compression;
uint8_t attr_1_alphadepth;
uint8_t attr_2_alphatype;
uint8_t attr_3_mipmaplevels;
int32_t resx;
int32_t resy;
int32_t offsets[16];
int32_t sizes[16];
};
#pragma pack(pop)
#include <boost/thread.hpp>
#include <noggit/MPQ.h>
void blp_texture::bind()
{
opengl::texture::bind();
if (!finished)
{
return;
}
if (!_uploaded)
{
upload();
}
}
void blp_texture::upload()
{
if (_uploaded)
{
return;
}
int width = _width, height = _height;
if (!_compression_format)
{
for (int i = 0; i < _data.size(); ++i)
{
gl.texImage2D(GL_TEXTURE_2D, i, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, _data[i].data());
width = std::max(width >> 1, 1);
height = std::max(height >> 1, 1);
}
_data.clear();
}
else
{
for (int i = 0; i < _compressed_data.size(); ++i)
{
gl.compressedTexImage2D(GL_TEXTURE_2D, i, _compression_format.get(), width, height, 0, _compressed_data[i].size(), _compressed_data[i].data());
width = std::max(width >> 1, 1);
height = std::max(height >> 1, 1);
}
gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, _compressed_data.size() - 1);
_compressed_data.clear();
}
gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
_uploaded = true;
}
void blp_texture::loadFromUncompressedData(BLPHeader const* lHeader, char const* lData)
{
unsigned int const* pal = reinterpret_cast<unsigned int const*>(lData + sizeof(BLPHeader));
unsigned char const* buf;
unsigned int *p;
unsigned char const* c;
unsigned char const* a;
int alphabits = lHeader->attr_1_alphadepth;
bool hasalpha = alphabits != 0;
int width = _width, height = _height;
for (int i = 0; i<16; ++i)
{
width = std::max(1, width);
height = std::max(1, height);
if (lHeader->offsets[i] > 0 && lHeader->sizes[i] > 0)
{
buf = reinterpret_cast<unsigned char const*>(&lData[lHeader->offsets[i]]);
std::vector<uint32_t> data(lHeader->sizes[i]);
int cnt = 0;
p = data.data();
c = buf;
a = buf + width*height;
for (int y = 0; y<height; y++)
{
for (int x = 0; x<width; x++)
{
unsigned int k = pal[*c++];
k = ((k & 0x00FF0000) >> 16) | ((k & 0x0000FF00)) | ((k & 0x000000FF) << 16);
int alpha = 0xFF;
if (hasalpha)
{
if (alphabits == 8)
{
alpha = (*a++);
}
else if (alphabits == 1)
{
alpha = (*a & (1 << cnt++)) ? 0xff : 0;
if (cnt == 8)
{
cnt = 0;
a++;
}
}
}
k |= alpha << 24;
*p++ = k;
}
}
_data[i] = data;
}
else
{
return;
}
width >>= 1;
height >>= 1;
}
}
void blp_texture::loadFromCompressedData(BLPHeader const* lHeader, char const* lData)
{
// 0 (0000) & 3 == 0 1 (0001) & 3 == 1 7 (0111) & 3 == 3
const int alphatypes[] = { GL_COMPRESSED_RGB_S3TC_DXT1_EXT, GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT };
const int blocksizes[] = { 8, 16, 0, 16 };
int alpha_type = lHeader->attr_2_alphatype & 3;
GLint format = alphatypes[alpha_type];
_compression_format = format == GL_COMPRESSED_RGB_S3TC_DXT1_EXT ? (lHeader->attr_1_alphadepth == 1 ? GL_COMPRESSED_RGBA_S3TC_DXT1_EXT : GL_COMPRESSED_RGB_S3TC_DXT1_EXT) : format;
int width = _width, height = _height;
for (int i = 0; i < 16; ++i)
{
if (lHeader->sizes[i] <= 0 || lHeader->offsets[i] <= 0)
{
return;
}
// make sure the vector is of the right size, blizzard seems to fuck those up for some small mipmaps
int size = std::floor((width + 3) / 4) * std::floor((height + 3) / 4) * blocksizes[alpha_type];
if (size < lHeader->sizes[i])
{
LogDebug << "mipmap size mismatch in '" << filename << "'" << std::endl;
return;
}
_compressed_data[i].resize(size);
char const* start = lData + lHeader->offsets[i];
std::copy(start, start + lHeader->sizes[i], _compressed_data[i].begin());
width = std::max(width >> 1, 1);
height = std::max(height >> 1, 1);
}
}
blp_texture::blp_texture(const std::string& filenameArg)
: AsyncObject(filenameArg)
{
}
void blp_texture::finishLoading()
{
bool exists = MPQFile::exists(filename);
if (!exists)
{
LogError << "file not found: '" << filename << "'" << std::endl;
}
MPQFile f(exists ? filename : "textures/shanecube.blp");
if (f.isEof())
{
finished = true;
throw std::runtime_error ("File " + filename + " does not exist");
}
char const* lData = f.getPointer();
BLPHeader const* lHeader = reinterpret_cast<BLPHeader const*>(lData);
_width = lHeader->resx;
_height = lHeader->resy;
if (lHeader->attr_0_compression == 1)
{
loadFromUncompressedData(lHeader, lData);
}
else if (lHeader->attr_0_compression == 2)
{
loadFromCompressedData(lHeader, lData);
}
else
{
finished = true;
throw std::logic_error ("unimplemented BLP colorEncoding");
}
f.close();
finished = true;
_state_changed.notify_all();
}
namespace noggit
{
QPixmap render_blp_to_pixmap ( std::string const& blp_filename
, int width
, int height
)
{
opengl::context::save_current_context const context_save (::gl);
QOpenGLContext context;
context.create();
QOpenGLFramebufferObjectFormat fmt;
fmt.setSamples(1);
fmt.setInternalTextureFormat(GL_RGBA8);
QOffscreenSurface surface;
surface.create();
context.makeCurrent(&surface);
opengl::context::scoped_setter const context_set (::gl, &context);
opengl::scoped::bool_setter<GL_CULL_FACE, GL_FALSE> cull;
opengl::scoped::bool_setter<GL_DEPTH_TEST, GL_FALSE> depth;
opengl::scoped::deferred_upload_vertex_arrays<1> vao;
vao.upload();
opengl::scoped::deferred_upload_buffers<3> buffers;
buffers.upload();
GLuint const& indices_vbo = buffers[0];
GLuint const& vertices_vbo = buffers[1];
GLuint const& texcoords_vbo = buffers[2];
opengl::texture::set_active_texture(0);
blp_texture texture(blp_filename);
texture.finishLoading();
width = width == -1 ? texture.width() : width;
height = height == -1 ? texture.height() : height;
float h = static_cast<float>(height);
float w = static_cast<float>(width);
float half_h = h * 0.5f;
float half_w = w * 0.5f;
std::vector<math::vector_2d> vertices =
{
{-half_w, -half_h}
,{-half_w, half_h}
,{ half_w, half_h}
,{ half_w, -half_h}
};
std::vector<math::vector_2d> texcoords =
{
{0.f, 0.f}
,{0.f, h}
,{w, h}
,{w, 0.f}
};
std::vector<std::uint16_t> indices = {0,1,2, 2,3,0};
gl.bufferData<GL_ARRAY_BUFFER, math::vector_2d>(vertices_vbo, vertices, GL_STATIC_DRAW);
gl.bufferData<GL_ARRAY_BUFFER, math::vector_2d>(texcoords_vbo, texcoords, GL_STATIC_DRAW);
gl.bufferData<GL_ELEMENT_ARRAY_BUFFER, std::uint16_t>(indices_vbo, indices, GL_STATIC_DRAW);
QOpenGLFramebufferObject pixel_buffer(width, height, fmt);
pixel_buffer.bind();
gl.viewport(0, 0, w, h);
gl.clearColor(.0f, .0f, .0f, 1.f);
gl.clear(GL_COLOR_BUFFER_BIT);
opengl::program program
(
{
{
GL_VERTEX_SHADER, R"code(
#version 330 core
in vec4 position;
in vec2 tex_coord;
out vec2 f_tex_coord;
void main()
{
f_tex_coord = vec2(tex_coord.x, -tex_coord.y);
gl_Position = position;
}
)code"
},
{
GL_FRAGMENT_SHADER,
R"code(
#version 330 core
uniform sampler2D tex;
in vec2 f_tex_coord;
layout(location = 0) out vec4 out_color;
void main()
{
out_color = vec4(texture(tex, f_tex_coord/2.f + vec2(0.5)).rgb, 1.);
}
)code"
}
}
);
opengl::scoped::use_program shader (program);
shader.uniform("tex", 0);
texture.bind();
opengl::scoped::vao_binder const _ (vao[0]);
{
opengl::scoped::buffer_binder<GL_ARRAY_BUFFER> vertices_binder (vertices_vbo);
shader.attrib("position", 2, GL_FLOAT, GL_FALSE, 0, 0);
}
{
opengl::scoped::buffer_binder<GL_ARRAY_BUFFER> texcoords_binder (texcoords_vbo);
shader.attrib("tex_coord", 2, GL_FLOAT, GL_FALSE, 0, 0);
}
opengl::scoped::buffer_binder<GL_ELEMENT_ARRAY_BUFFER> indices_binder(indices_vbo);
gl.drawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, nullptr);
QPixmap pixmap (QPixmap::fromImage (pixel_buffer.toImage()));
pixel_buffer.release();
if (pixmap.isNull())
{
throw std::runtime_error
("failed rendering " + blp_filename + " to pixmap");
}
return pixmap;
}
}
scoped_blp_texture_reference::scoped_blp_texture_reference (std::string const& filename)
: _blp_texture (TextureManager::_.emplace (filename))
{}
scoped_blp_texture_reference::scoped_blp_texture_reference (scoped_blp_texture_reference const& other)
: _blp_texture (other._blp_texture ? TextureManager::_.emplace (other._blp_texture->filename) : nullptr)
{}
void scoped_blp_texture_reference::Deleter::operator() (blp_texture* texture) const
{
TextureManager::_.erase (texture->filename);
}
blp_texture* scoped_blp_texture_reference::operator->() const
{
return _blp_texture.get();
}
blp_texture* scoped_blp_texture_reference::get() const
{
return _blp_texture.get();
}
bool scoped_blp_texture_reference::operator== (scoped_blp_texture_reference const& other) const
{
return std::tie (_blp_texture) == std::tie (other._blp_texture);
}