1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
|
#include "texture.hh"
#include "configuration.hh"
#include "video_driver.hh"
#include "screen.hh"
#include "svg.hh"
#include <boost/algorithm/string/case_conv.hpp>
#include <boost/filesystem.hpp>
#include <atomic>
#include <cctype>
#include <condition_variable>
#include <stdexcept>
#include <sstream>
#include <thread>
#include <vector>
using std::uint32_t;
Shader& getShader(std::string const& name) {
return Game::getSingletonPtr()->window().shader(name); // FIXME
}
float Dimensions::screenY() const {
switch (m_screenAnchor) {
case CENTER: return 0.0;
case TOP: return -0.5 * virtH();
case BOTTOM: return 0.5 * virtH();
}
throw std::logic_error("Dimensions::screenY(): unknown m_screenAnchor value");
}
struct Job {
fs::path name;
typedef std::function<void (Bitmap& bitmap)> ApplyFunc;
ApplyFunc apply;
Bitmap bitmap;
Job() {}
Job(fs::path const& n, ApplyFunc const& a): name(n), apply(a) {}
};
class TextureLoader::Impl {
/// Load a file from disk into a buffer
static void load(Bitmap& bitmap, fs::path const& name) {
try {
std::string ext = boost::algorithm::to_lower_copy(name.extension().string());
if (!fs::is_regular_file(name)) throw std::runtime_error("File not found: " + name.string());
else if (ext == ".svg") loadSVG(bitmap, name);
else if (ext == ".jpg" || ext == ".jpeg") loadJPEG(bitmap, name);
else if (ext == ".png") loadPNG(bitmap, name);
else throw std::runtime_error("Unknown image file format: " + name.string());
} catch (std::exception& e) {
std::clog << "image/error: " << e.what() << std::endl;
}
}
std::atomic<bool> m_quit{ false };
std::mutex m_mutex;
std::condition_variable m_condition;
typedef std::map<void const*, Job> Jobs;
Jobs m_jobs;
std::thread m_thread;
public:
Impl(): m_quit(), m_thread(&Impl::run, this) {}
~Impl() { m_quit = true; m_condition.notify_one(); m_thread.join(); }
/// The loader main loop: poll for image load jobs and load into RAM
void run() {
while (!m_quit) {
void const* target = nullptr;
fs::path name;
{
// Poll for jobs to be done
std::unique_lock<std::mutex> l(m_mutex);
for (auto& job: m_jobs) {
if (job.second.name.empty()) continue; // Job already done
name = job.second.name;
target = job.first;
break;
}
// If not found, wait for one
if (!target) {
m_condition.wait(l);
continue;
}
}
// Load image file into buffer
Bitmap bitmap;
load(bitmap, name);
// Store the result
std::lock_guard<std::mutex> l(m_mutex);
auto it = m_jobs.find(target);
if (it == m_jobs.end()) continue; // The job has been removed already
it->second.name.clear(); // Mark the job completed
it->second.bitmap.swap(bitmap); // Store the bitmap (if we got any)
}
}
/// Add a new job, using calling Texture's address as unique ID.
void push(void const* t, Job const& job) {
std::lock_guard<std::mutex> l(m_mutex);
m_jobs[t] = job;
m_condition.notify_one();
}
/// Cancel a job in progress (no effect if the job has already completed)
void remove(void const* t) {
std::lock_guard<std::mutex> l(m_mutex);
m_jobs.erase(t);
}
/// Upload all completed jobs to OpenGL (must be called from a valid OpenGL context)
void apply() {
std::lock_guard<std::mutex> l(m_mutex);
for (auto it = m_jobs.begin(); it != m_jobs.end();) {
{
Job& j = it->second;
if (!j.name.empty()) { ++it; continue; } // Job incomplete, skip it
j.apply(j.bitmap); // Upload to OpenGL
}
m_jobs.erase(it++);
}
}
};
std::unique_ptr<TextureLoader::Impl> ldr = nullptr;
TextureLoader::TextureLoader() {
if (ldr) throw std::logic_error("Texture Loader initialized twice. There can be only one.");
ldr = std::make_unique<Impl>();
}
TextureLoader::~TextureLoader() { ldr.reset(); }
void updateTextures() { ldr->apply(); }
template <typename T> void loader(T* target, fs::path const& name) {
// Temporarily add 1x1 pixel black texture
Bitmap bitmap;
bitmap.fmt = pix::RGB;
bitmap.resize(1, 1);
target->load(bitmap);
// Ask the loader to retrieve the image
ldr->push(target, Job(name, [target](Bitmap& bitmap){ target->load(bitmap); }));
}
Texture::Texture(fs::path const& filename) { loader(this, filename); }
Texture::~Texture() { ldr->remove(this); }
// Stuff for converting pix::Format into OpenGL enum values & other flags
namespace {
struct PixFmt {
PixFmt(): swap() {} // Required by std::map
PixFmt(GLenum f, GLenum t, bool s): format(f), type(t), swap(s) {}
GLenum format;
GLenum type;
bool swap; // Reverse byte order
};
struct PixFormats {
typedef std::map<pix::Format, PixFmt> Map;
Map m;
PixFormats() {
using namespace pix;
m[RGB] = PixFmt(GL_RGB, GL_UNSIGNED_BYTE, false);
m[BGR] = PixFmt(GL_BGR, GL_UNSIGNED_BYTE, true);
m[CHAR_RGBA] = PixFmt(GL_RGBA, GL_UNSIGNED_BYTE, false);
m[INT_ARGB] = PixFmt(GL_BGRA, GL_UNSIGNED_INT_8_8_8_8, true);
}
} pixFormats;
PixFmt const& getPixFmt(pix::Format format) {
PixFormats::Map::const_iterator it = pixFormats.m.find(format);
if (it != pixFormats.m.end()) return it->second;
throw std::logic_error("Unknown pixel format");
}
GLint internalFormat(bool linear) {
return (!linear && GL_EXT_framebuffer_sRGB ? GL_SRGB_ALPHA : GL_RGBA);
}
}
void Texture::load(Bitmap const& bitmap, bool isText) {
glutil::GLErrorChecker glerror("Texture::load");
// Initialize dimensions
m_width = bitmap.width; m_height = bitmap.height;
dimensions = Dimensions(bitmap.ar).fixedWidth(1.0f);
m_premultiplied = bitmap.linearPremul;
UseTexture texture(*this);
// When texture area is small, bilinear filter the closest mipmap
glTexParameterf(type(), GL_TEXTURE_MIN_FILTER, isText ? GL_LINEAR : GL_LINEAR_MIPMAP_NEAREST);
// When texture area is large, bilinear filter the original
glTexParameterf(type(), GL_TEXTURE_MAG_FILTER, isText ? GL_NEAREST : GL_LINEAR);
if (!isText) glTexParameterf(type(), GL_TEXTURE_MAX_LEVEL, 4);
glerror.check("glTexParameterf");
// Anisotropy is potential trouble maker
if (epoxy_has_gl_extension("GL_EXT_texture_filter_anisotropic")) {
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 16.0f);
glerror.check("MAX_ANISOTROPY_EXT");
}
// Load the data into texture
PixFmt const& f = getPixFmt(bitmap.fmt);
glPixelStorei(GL_UNPACK_SWAP_BYTES, f.swap);
glTexImage2D(type(), 0, internalFormat(bitmap.linearPremul), bitmap.width, bitmap.height, 0, f.format, f.type, bitmap.data());
if (!isText) glGenerateMipmap(type());
}
void Texture::draw() const {
if (empty()) return;
// FIXME: This gets image alpha handling right but our ColorMatrix system always assumes premultiplied alpha
// (will produce incorrect results for fade effects)
glBlendFunc(m_premultiplied ? GL_ONE : GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
draw(dimensions, TexCoords(tex.x1, tex.y1, tex.x2, tex.y2));
}
|