Open source Star Ruler 2 source code!
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
#include "resource/library.h"
|
||||
#include "main/console.h"
|
||||
#include "main/logging.h"
|
||||
#include "files.h"
|
||||
#include <set>
|
||||
|
||||
#include "render/gl_shader.h"
|
||||
|
||||
namespace resource {
|
||||
|
||||
threads::Mutex reloadTextureMutex;
|
||||
std::set<std::string> reloadTextures;
|
||||
|
||||
threads::Mutex reloadShaderMutex;
|
||||
std::set<std::string> reloadShaders;
|
||||
|
||||
threads::Mutex reloadSkinMutex;
|
||||
std::set<std::string> reloadSkins;
|
||||
|
||||
void Library::clearWatchedResources() {
|
||||
clearWatches();
|
||||
|
||||
{
|
||||
threads::Lock lock(reloadTextureMutex);
|
||||
reloadTextures.clear();
|
||||
}
|
||||
|
||||
{
|
||||
threads::Lock lock(reloadShaderMutex);
|
||||
reloadShaders.clear();
|
||||
}
|
||||
|
||||
{
|
||||
threads::Lock lock(reloadSkinMutex);
|
||||
reloadSkins.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void Library::watchTexture(const std::string& filename) {
|
||||
//info("Watching texture '%s'", filename.c_str());
|
||||
watchFile(filename, [filename]() -> bool {
|
||||
threads::Lock lock(reloadTextureMutex);
|
||||
if(reloadTextures.find(filename) == reloadTextures.end()) {
|
||||
reloadTextures.insert(filename);
|
||||
info("Reloading texture '%s'", filename.c_str());
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void Library::watchSkin(const std::string& filename) {
|
||||
//info("Watching skin '%s'", filename.c_str());
|
||||
watchFile(filename, [filename]() -> bool {
|
||||
threads::Lock lock(reloadSkinMutex);
|
||||
if(reloadSkins.find(filename) == reloadSkins.end()) {
|
||||
reloadSkins.insert(filename);
|
||||
info("Reloading skin '%s'", filename.c_str());
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void Library::watchShader(const std::string& shadername, const std::string& filename) {
|
||||
std::string absFilename = getAbsolutePath(filename);
|
||||
//info("Watching shader '%s' (%s)", shadername.c_str(), filename.c_str());
|
||||
watchFile(filename, [absFilename,shadername]() -> bool {
|
||||
threads::Lock lock(reloadShaderMutex);
|
||||
if(reloadShaders.find(absFilename) == reloadShaders.end()) {
|
||||
reloadShaders.insert(shadername);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void Library::reloadWatchedResources() {
|
||||
if(!reloadTextures.empty()) {
|
||||
threads::Lock lock(reloadTextureMutex);
|
||||
foreach(name, reloadTextures) {
|
||||
auto tex = texture_files.find(*name);
|
||||
if(tex == texture_files.end())
|
||||
continue;
|
||||
if(tex->second == 0)
|
||||
continue;
|
||||
|
||||
Image* img = loadImage(name->c_str());
|
||||
if(img) {
|
||||
tex->second->load(*img, tex->second->hasMipMaps);
|
||||
delete img;
|
||||
}
|
||||
}
|
||||
|
||||
reloadTextures.clear();
|
||||
|
||||
//Mark all spritesheets as dirty, or they won't properly handle changes in resolution
|
||||
foreach(sheet,spritesheets)
|
||||
sheet->second->dirty = true;
|
||||
}
|
||||
|
||||
if(!reloadShaders.empty()) {
|
||||
threads::Lock lock(reloadShaderMutex);
|
||||
foreach(name, reloadShaders) {
|
||||
auto program = programs.find(*name);
|
||||
if(program != programs.end()) {
|
||||
if(program->second->compile() != 0)
|
||||
error("-In Shader Program '%s'", program->first.c_str());
|
||||
foreach(shader, shaders) {
|
||||
if(shader->second->program == program->second) {
|
||||
info("Reloading shader (%s)", shader->first.c_str());
|
||||
if(shader->second->compile() != 0)
|
||||
error("-In Shader '%s'", shader->first.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reloadShaders.clear();
|
||||
}
|
||||
|
||||
if(!reloadSkins.empty()) {
|
||||
threads::Lock lock(reloadShaderMutex);
|
||||
foreach(name, reloadSkins) {
|
||||
auto skin = skin_files.find(*name);
|
||||
if(skin != skin_files.end())
|
||||
loadSkin(*name, skin->second);
|
||||
}
|
||||
|
||||
reloadSkins.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void Library::bindHotloading() {
|
||||
console.addCommand("reload", [this](argList& args) {
|
||||
if(args.empty()) {
|
||||
console.printLn("Specify exact name of shader to reload");
|
||||
return;
|
||||
}
|
||||
|
||||
auto shader = shaders.find(args[0]);
|
||||
if(shader != shaders.end()) {
|
||||
int result = shader->second->compile();
|
||||
if(result == 0)
|
||||
console.printLn("Successfully recompiled shader");
|
||||
else
|
||||
error("Recompilation of '%s' failed", shader->first.c_str());
|
||||
}
|
||||
else {
|
||||
std::string path = getAbsolutePath(args[0]);
|
||||
auto tex = texture_files.find(path);
|
||||
if(tex != texture_files.end()) {
|
||||
if(tex->second) {
|
||||
Image* img = loadImage(path.c_str());
|
||||
if(img) {
|
||||
tex->second->load(*img, tex->second->hasMipMaps);
|
||||
console.printLn("Texture reloaded");
|
||||
delete img;
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.printLn("Texture type cannot be reloaded");
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.printLn("No Shader or Texture match found");
|
||||
}
|
||||
}
|
||||
}, true );
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,314 @@
|
||||
#include "render/render_state.h"
|
||||
#include "main/references.h"
|
||||
#include "resource/library.h"
|
||||
#include "scene/particle_system.h"
|
||||
#include "ISoundSource.h"
|
||||
#include "files.h"
|
||||
#include "str_util.h"
|
||||
#include "num_util.h"
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
namespace resource {
|
||||
|
||||
void Library::clear() {
|
||||
foreach(mat, materials)
|
||||
delete mat->second;
|
||||
materials.clear();
|
||||
material_names.clear();
|
||||
|
||||
foreach(mat, matGroups)
|
||||
delete mat->second;
|
||||
matGroups.clear();
|
||||
|
||||
foreach(sheet, spritesheets)
|
||||
delete sheet->second;
|
||||
spritesheets.clear();
|
||||
spritesheet_names.clear();
|
||||
|
||||
foreach(tex, textures)
|
||||
delete *tex;
|
||||
textures.clear();
|
||||
texture_files.clear();
|
||||
clearTextures();
|
||||
|
||||
foreach(mesh, meshes)
|
||||
if(mesh->second != errors.mesh)
|
||||
delete mesh->second;
|
||||
meshes.clear();
|
||||
|
||||
foreach(shader, shaders)
|
||||
delete shader->second;
|
||||
shaders.clear();
|
||||
settingsShaders.clear();
|
||||
|
||||
foreach(program, programs)
|
||||
delete program->second;
|
||||
programs.clear();
|
||||
|
||||
foreach(font, font_list)
|
||||
delete *font;
|
||||
font_list.clear();
|
||||
fonts.clear();
|
||||
|
||||
foreach(skin, skins)
|
||||
delete skin->second;
|
||||
skin_files.clear();
|
||||
skins.clear();
|
||||
|
||||
foreach(sound, sounds)
|
||||
delete sound->second;
|
||||
sounds.clear();
|
||||
|
||||
clearWatchedResources();
|
||||
|
||||
delete errors.texture;
|
||||
errors.texture = 0;
|
||||
errors.material.textures[0] = 0;
|
||||
|
||||
delete errors.mesh;
|
||||
errors.mesh = 0;
|
||||
|
||||
mat_indices.clear();
|
||||
}
|
||||
void Library::prepErrorResources() {
|
||||
if(!devices.render) {
|
||||
errors.material.constant = true;
|
||||
errors.mesh = new ErrorMesh();
|
||||
return;
|
||||
}
|
||||
|
||||
errors.particleSystem = scene::createDummyParticleSystem();
|
||||
|
||||
errors.material.constant = true;
|
||||
errors.material.lighting = false;
|
||||
|
||||
errors.skin.materialName = "~invalid_skin";
|
||||
errors.mesh = devices.render->createMesh(Mesh());
|
||||
|
||||
render::RenderState* img2d = new render::RenderState();
|
||||
img2d->baseMat = render::MAT_Alpha;
|
||||
img2d->lighting = false;
|
||||
img2d->depthTest = render::DT_NoDepthTest;
|
||||
img2d->depthWrite = false;
|
||||
img2d->constant = true;
|
||||
materials["Image2D"] = img2d;
|
||||
material_names.push_back("Image2D");
|
||||
mat_indices[img2d] = material_names.size();
|
||||
}
|
||||
|
||||
void Library::generateErrorResources() {
|
||||
if(!devices.render)
|
||||
return;
|
||||
|
||||
{ //Checkboard error image
|
||||
Image img(64,64,FMT_RGB);
|
||||
for(unsigned r = 0; r < 64; ++r)
|
||||
for(unsigned x = 0; x < 64; ++x)
|
||||
img.rgb[x+(r*64)] = (x/2 % 2) ^ (r/2 % 2) ? ColorRGB(0,0,0) : ColorRGB(255,0,255);
|
||||
errors.texture = devices.render->createTexture(img);
|
||||
errors.material.textures[0] = errors.texture;
|
||||
}
|
||||
|
||||
{ //Tetrahedron error mesh
|
||||
Mesh mesh;
|
||||
mesh.vertices.push_back( Vertex(vec3f(0,1,0)) );
|
||||
mesh.vertices.push_back( Vertex(vec3f(1,0,0)) );
|
||||
mesh.vertices.push_back( Vertex(vec3f(-1,0,1)) );
|
||||
mesh.vertices.push_back( Vertex(vec3f(-1,0,-1)) );
|
||||
|
||||
mesh.faces.push_back(Mesh::Face(0,1,2));
|
||||
mesh.faces.push_back(Mesh::Face(0,2,3));
|
||||
mesh.faces.push_back(Mesh::Face(0,3,1));
|
||||
mesh.faces.push_back(Mesh::Face(1,2,3));
|
||||
|
||||
errors.mesh->resetToMesh(mesh);
|
||||
}
|
||||
}
|
||||
|
||||
void Library::load(ResourceType type, const std::string& filename) {
|
||||
switch(type) {
|
||||
case RT_Sound:
|
||||
loadSounds(filename);
|
||||
break;
|
||||
case RT_Material:
|
||||
case RT_SpriteSheet:
|
||||
loadMaterials(filename);
|
||||
break;
|
||||
case RT_Mesh:
|
||||
loadModels(filename);
|
||||
break;
|
||||
case RT_Font:
|
||||
loadFonts(filename);
|
||||
break;
|
||||
case RT_Shader:
|
||||
loadShaders(filename);
|
||||
break;
|
||||
case RT_Skin:
|
||||
loadSkins(filename);
|
||||
break;
|
||||
case RT_ParticleSystem:
|
||||
{
|
||||
auto* pSys = scene::loadParticleSystem(filename);
|
||||
if(pSys)
|
||||
particleSystems[getBasename(filename,false)] = pSys;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Library::loadDirectory(ResourceType type, const std::string& filename) {
|
||||
std::vector<std::string> files;
|
||||
std::string dirname(filename);
|
||||
|
||||
listDirectory(dirname, files);
|
||||
foreach(it, files) {
|
||||
std::string& file = *it;
|
||||
if(file.size() < 4)
|
||||
continue;
|
||||
if(!file.compare(file.size() - 4, 4, ".txt"))
|
||||
load(type, path_join(dirname, file));
|
||||
}
|
||||
}
|
||||
|
||||
Library::ResourceAccessor Library::operator[](const char* name) const {
|
||||
ResourceAccessor access;
|
||||
access.source = this;
|
||||
access.name = name;
|
||||
return access;
|
||||
}
|
||||
|
||||
#define access_ref(type, func) \
|
||||
Library::ResourceAccessor::operator const type &() { return source->func(name); }\
|
||||
Library::ResourceAccessor::operator const type *() { return &source->func(name); }
|
||||
|
||||
#define access_ptr(type, func) \
|
||||
Library::ResourceAccessor::operator const type &() { return *source->func(name); }\
|
||||
Library::ResourceAccessor::operator const type *() { return source->func(name); }
|
||||
|
||||
access_ref(render::RenderMesh, getMesh);
|
||||
access_ref(render::RenderState, getMaterial);
|
||||
access_ref(render::Font, getFont);
|
||||
access_ref(render::SpriteSheet, getSpriteSheet);
|
||||
access_ref(gui::skin::Skin, getSkin);
|
||||
|
||||
access_ptr(Sound, getSound);
|
||||
access_ptr(render::Shader, getShader);
|
||||
|
||||
const render::RenderState& Library::getErrorMaterial() const {
|
||||
return errors.material;
|
||||
}
|
||||
|
||||
const render::Texture* Library::getErrorTexture() const {
|
||||
return errors.texture;
|
||||
}
|
||||
|
||||
const render::SpriteSheet& Library::getErrorSpriteSheet() const {
|
||||
return errors.spriteSheet;
|
||||
}
|
||||
|
||||
const render::RenderState& Library::getMaterial(const std::string& name) const {
|
||||
auto mat = materials.find(name);
|
||||
if(mat == materials.end())
|
||||
return errors.material;
|
||||
else
|
||||
return *mat->second;
|
||||
}
|
||||
|
||||
const render::MaterialGroup& Library::getMaterialGroup(const std::string& name) const {
|
||||
auto mat = matGroups.find(name);
|
||||
if(mat == matGroups.end())
|
||||
return errors.group;
|
||||
else
|
||||
return *mat->second;
|
||||
}
|
||||
|
||||
const render::SpriteSheet& Library::getSpriteSheet(const std::string& name) const {
|
||||
auto sheet = spritesheets.find(name);
|
||||
if(sheet == spritesheets.end())
|
||||
return errors.spriteSheet;
|
||||
else
|
||||
return *sheet->second;
|
||||
}
|
||||
|
||||
const render::RenderMesh& Library::getMesh(const std::string& name) const {
|
||||
auto mesh = meshes.find(name);
|
||||
if(mesh == meshes.end())
|
||||
return *errors.mesh;
|
||||
else
|
||||
return *mesh->second;
|
||||
}
|
||||
|
||||
const gui::skin::Skin& Library::getSkin(const std::string& name) const {
|
||||
auto skin = skins.find(name);
|
||||
if(skin == skins.end())
|
||||
return errors.skin;
|
||||
else
|
||||
return *skin->second;
|
||||
}
|
||||
|
||||
const scene::ParticleSystemDesc* Library::getParticleSystem(const std::string& name) const {
|
||||
auto sys = particleSystems.find(name);
|
||||
if(sys == particleSystems.end())
|
||||
return errors.particleSystem;
|
||||
else
|
||||
return sys->second;
|
||||
}
|
||||
|
||||
|
||||
//TODO: Decide how to handle the error font (it should still render some text)
|
||||
const render::Font& Library::getFont(const std::string& name) const {
|
||||
auto font = fonts.find(name);
|
||||
if(font == fonts.end())
|
||||
return *fonts.begin()->second;
|
||||
else
|
||||
return *font->second;
|
||||
}
|
||||
|
||||
const render::Shader* Library::getShader(const std::string& name) const {
|
||||
auto shader = shaders.find(name);
|
||||
if(shader == shaders.end())
|
||||
return 0;
|
||||
else
|
||||
return shader->second;
|
||||
}
|
||||
|
||||
const Sound* Library::getSound(const std::string& name) const {
|
||||
auto sound = sounds.find(name);
|
||||
if(sound == sounds.end())
|
||||
return 0;
|
||||
else
|
||||
return sound->second;
|
||||
}
|
||||
|
||||
render::Sprite Library::getSprite(const std::string& desc) {
|
||||
render::Sprite sprt;
|
||||
std::string tmp = desc;
|
||||
auto pos = desc.find("*");
|
||||
if(pos != std::string::npos && pos < tmp.size() - 1) {
|
||||
sprt.color = toColor(trim(tmp.substr(pos+1)));
|
||||
tmp = trim(tmp.substr(0, pos));
|
||||
}
|
||||
pos = tmp.find("::");
|
||||
if(pos == std::string::npos || pos >= tmp.size() - 2) {
|
||||
auto sheet = spritesheets.find(tmp);
|
||||
if(sheet != spritesheets.end()) {
|
||||
sprt.sheet = sheet->second;
|
||||
sprt.index = 0;
|
||||
}
|
||||
else {
|
||||
auto img = materials.find(tmp);
|
||||
if(img != materials.end()) {
|
||||
sprt.mat = img->second;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
sprt.sheet = &getSpriteSheet(tmp.substr(0, pos));
|
||||
sprt.index = toNumber<unsigned>(tmp.substr(pos+2));
|
||||
}
|
||||
return sprt;
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,247 @@
|
||||
#pragma once
|
||||
#include "compat/misc.h"
|
||||
#include "render/render_state.h"
|
||||
#include "render/render_mesh.h"
|
||||
#include "mesh.h"
|
||||
#include "render/driver.h"
|
||||
#include "render/texture.h"
|
||||
#include "render/spritesheet.h"
|
||||
#include "render/shader.h"
|
||||
#include "gui/skin.h"
|
||||
#include "str_util.h"
|
||||
#include <climits>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace audio {
|
||||
class ISoundSource;
|
||||
class ISound;
|
||||
};
|
||||
|
||||
namespace scene {
|
||||
struct ParticleSystemDesc;
|
||||
};
|
||||
|
||||
const Mesh emptyMesh;
|
||||
|
||||
namespace resource {
|
||||
|
||||
enum ResourceType {
|
||||
RT_Sound,
|
||||
RT_Material,
|
||||
RT_Mesh,
|
||||
RT_Shader,
|
||||
RT_Font,
|
||||
RT_Skin,
|
||||
RT_SpriteSheet,
|
||||
RT_ParticleSystem
|
||||
};
|
||||
|
||||
class Sound {
|
||||
public:
|
||||
audio::ISoundSource* source;
|
||||
std::string streamSource;
|
||||
float volume;
|
||||
bool loaded;
|
||||
Sound() : source(0), volume(1.f), loaded(false) {}
|
||||
|
||||
audio::ISound* play2D(bool loop, bool pause, bool priority) const;
|
||||
audio::ISound* play3D(const vec3d& pos, bool loop, bool pause, bool priority) const;
|
||||
};
|
||||
|
||||
struct ShaderGlobal {
|
||||
std::string name;
|
||||
render::Shader::VarType type;
|
||||
unsigned arraySize;
|
||||
int size;
|
||||
void* ptr;
|
||||
|
||||
ShaderGlobal();
|
||||
|
||||
template<class T>
|
||||
void setValue(unsigned index, T& v) const {
|
||||
if(index < arraySize)
|
||||
((T*)ptr)[index] = v;
|
||||
}
|
||||
};
|
||||
|
||||
render::Texture* queueImage(Image* img, int priority = 0, bool mipmap = true, bool cachePixels = false);
|
||||
void queueTextureUpdate(render::Texture* tex, Image* img, int priority = 0, bool mipmap = true, bool cachePixels = false);
|
||||
|
||||
class Library {
|
||||
void loadMaterials(const std::string& filename);
|
||||
void loadFonts(const std::string& filename);
|
||||
void loadModels(const std::string& filename);
|
||||
void loadShaders(const std::string& filename);
|
||||
void loadSkins(const std::string& filename);
|
||||
void loadSkin(const std::string& filename, gui::skin::Skin* skin);
|
||||
void loadSounds(const std::string& filename);
|
||||
|
||||
static void initSkinNames();
|
||||
void clearTextures();
|
||||
|
||||
class ErrorShader : public render::Shader {
|
||||
int compile() { return 0; }
|
||||
void bind(float*) const {}
|
||||
void updateDynamicVars() const {}
|
||||
void saveDynamicVars(float*) const { }
|
||||
void loadDynamicVars(float*) const { }
|
||||
public:
|
||||
ErrorShader() { constant = true; dynamicFloats = 0; }
|
||||
};
|
||||
|
||||
class ErrorMesh : public render::RenderMesh {
|
||||
AABBoxf box;
|
||||
void resetToMesh(const Mesh& mesh) {}
|
||||
const RenderMesh* selectLOD(double distance) const { return this; }
|
||||
void setLOD(double distance, const RenderMesh* mesh) {}
|
||||
const AABBoxf& getBoundingBox() const { return box; }
|
||||
unsigned getMeshBytes() const { return 0; }
|
||||
const Mesh& getMesh() const { return emptyMesh; }
|
||||
|
||||
void render() const {};
|
||||
};
|
||||
|
||||
struct {
|
||||
render::Texture* texture;
|
||||
render::RenderState material;
|
||||
render::MaterialGroup group;
|
||||
render::RenderMesh* mesh;
|
||||
scene::ParticleSystemDesc* particleSystem;
|
||||
render::SpriteSheet spriteSheet;
|
||||
ErrorShader shader;
|
||||
gui::skin::Skin skin;
|
||||
} errors;
|
||||
public:
|
||||
struct ResourceAccessor;
|
||||
|
||||
umap<std::string, Sound*> sounds;
|
||||
umap<std::string, render::RenderState*> materials;
|
||||
umap<std::string, render::MaterialGroup*> matGroups;
|
||||
umap<std::string, render::RenderMesh*> meshes;
|
||||
umap<std::string, render::Shader*> shaders;
|
||||
umap<std::string, render::ShaderProgram*> programs;
|
||||
std::vector<render::Shader*> settingsShaders;
|
||||
umap<std::string, render::Font*> fonts;
|
||||
std::vector<render::Font*> font_list;
|
||||
umap<std::string, gui::skin::Skin*> skins;
|
||||
umap<std::string, scene::ParticleSystemDesc*> particleSystems;
|
||||
umap<std::string, render::SpriteSheet*> spritesheets;
|
||||
std::vector<render::Texture*> textures;
|
||||
umap<std::string, render::Texture*> texture_files;
|
||||
umap<std::string, gui::skin::Skin*> skin_files;
|
||||
|
||||
std::vector<std::string> material_names;
|
||||
std::vector<std::string> spritesheet_names;
|
||||
|
||||
umap<const render::RenderState*, unsigned> mat_indices;
|
||||
umap<const render::SpriteSheet*, unsigned> sheet_indices;
|
||||
|
||||
ResourceAccessor operator[](const char* name) const;
|
||||
|
||||
//Returns the specified material, or an error material if the material is not loaded
|
||||
const render::RenderState& getMaterial(const std::string& name) const;
|
||||
|
||||
//Returns the specified material group, or an error material group if the material group is not loaded
|
||||
const render::MaterialGroup& getMaterialGroup(const std::string& name) const;
|
||||
|
||||
//Returns the specified spritesheet, or an error spritesheet if it is not loaded
|
||||
const render::SpriteSheet& getSpriteSheet(const std::string& name) const;
|
||||
|
||||
//Returns the specified mesh, or an error mesh if the mesh is not loaded
|
||||
const render::RenderMesh& getMesh(const std::string& name) const;
|
||||
|
||||
//Returns the specified skin, or an error skin if the skin is not loaded
|
||||
const gui::skin::Skin& getSkin(const std::string& name) const;
|
||||
|
||||
//Returns the specified font, or an error font if the font is not loaded
|
||||
const render::Font& getFont(const std::string& name) const;
|
||||
|
||||
//Returns the specified particle system, or an error particle system if the system is not loaded
|
||||
const scene::ParticleSystemDesc* getParticleSystem(const std::string& name) const;
|
||||
|
||||
//Returns the specified texture, or an error shader (possibly 0) if the shader is not loaded
|
||||
const render::Shader* getShader(const std::string& name) const;
|
||||
|
||||
//Returns the specified sound, or an error sound (possibly 0) if the sound is missing
|
||||
const Sound* getSound(const std::string& name) const;
|
||||
|
||||
//Returns a reference to a particular sprite from a description
|
||||
render::Sprite getSprite(const std::string& desc);
|
||||
|
||||
//Returns the descriptor string for a particular sprite
|
||||
std::string getSpriteDesc(const render::Sprite& sprt) const;
|
||||
|
||||
//Creates fallback resources for when a resource is missing
|
||||
void prepErrorResources();
|
||||
void generateErrorResources();
|
||||
|
||||
const render::RenderState& getErrorMaterial() const;
|
||||
const render::SpriteSheet& getErrorSpriteSheet() const;
|
||||
const render::Texture* getErrorTexture() const;
|
||||
|
||||
//Clears all resources held by the library
|
||||
void clear();
|
||||
|
||||
void load(ResourceType type, const std::string& filename);
|
||||
void loadDirectory(ResourceType type, const std::string& filename);
|
||||
|
||||
void bindHotloading();
|
||||
void watchTexture(const std::string& filename);
|
||||
void watchShader(const std::string& shadername, const std::string& filename);
|
||||
void watchSkin(const std::string& skinname);
|
||||
|
||||
//Bind materials to skins
|
||||
void bindSkinMaterials();
|
||||
//Bind fonts to skins
|
||||
void bindSkinFonts();
|
||||
//Compile all loaded shaders
|
||||
void compileShaders();
|
||||
//Clear shader global variables
|
||||
void clearShaderGlobals();
|
||||
//Iterate over all shader globals
|
||||
void iterateShaderGlobals(std::function<void(std::string&,ShaderGlobal*)> func);
|
||||
//Compiles any watched resources that need reloaded
|
||||
void reloadWatchedResources();
|
||||
//Clears list of watched resources (called by clear)
|
||||
void clearWatchedResources();
|
||||
|
||||
//Load queued sounds
|
||||
bool hasQueuedSounds();
|
||||
bool processSounds(int maxPriority = INT_MIN, int amount = INT_MAX);
|
||||
|
||||
//Load queued images in the background
|
||||
bool hasQueuedImages();
|
||||
bool processImages(int maxPriority = INT_MIN, int amount = INT_MAX);
|
||||
|
||||
//Load queued textures - must be called from the thread that handles the driver
|
||||
bool hasQueuedTextures();
|
||||
bool processTextures(int maxPriority = INT_MIN, bool singleFrame = false);
|
||||
|
||||
//Load queued meshes - must be called from the thread that handles the driver
|
||||
bool hasQueuedMeshes();
|
||||
bool processMeshes(int maxPriority = INT_MIN, int amount = INT_MAX);
|
||||
|
||||
struct ResourceAccessor {
|
||||
const Library* source;
|
||||
const char* name;
|
||||
|
||||
operator const Sound*();
|
||||
operator const render::RenderState*();
|
||||
operator const render::RenderMesh*();
|
||||
operator const render::Shader*();
|
||||
operator const render::Font*();
|
||||
operator const render::SpriteSheet*();
|
||||
operator const gui::skin::Skin*();
|
||||
|
||||
operator const Sound&();
|
||||
operator const render::RenderState&();
|
||||
operator const render::RenderMesh&();
|
||||
operator const render::Shader&();
|
||||
operator const render::Font&();
|
||||
operator const render::SpriteSheet&();
|
||||
operator const gui::skin::Skin&();
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
@@ -0,0 +1,115 @@
|
||||
#include "main/references.h"
|
||||
#include "resource/library.h"
|
||||
#include "main/logging.h"
|
||||
#include "str_util.h"
|
||||
#include "num_util.h"
|
||||
#include "main/tick.h"
|
||||
#include "render/font.h"
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
namespace resource {
|
||||
|
||||
void Library::loadFonts(const std::string& filename) {
|
||||
std::string font_name, font_file, font_locale;
|
||||
std::vector<std::pair<int,int>> pages;
|
||||
std::vector<std::string> replaces;
|
||||
int size = 12;
|
||||
render::Font* bold = 0;
|
||||
render::Font* italic = 0;
|
||||
|
||||
auto makeFont = [&]() {
|
||||
if(font_name.empty() || font_file.empty())
|
||||
return;
|
||||
if(font_locale.empty() || font_locale == game_locale) {
|
||||
render::Font* font = 0;
|
||||
auto p_ext = strrchr(font_file.c_str(), '.');
|
||||
if(!p_ext || strcmp_nocase(p_ext, ".fnt") == 0)
|
||||
font = render::loadFontFNT(devices.render, font_file.c_str());
|
||||
else if(strcmp_nocase(p_ext, ".ttf") == 0)
|
||||
font = render::loadFontFT2(*devices.render, font_file.c_str(), pages, size);
|
||||
else if(strcmp_nocase(p_ext, ".otf") == 0)
|
||||
font = render::loadFontFT2(*devices.render, font_file.c_str(), pages, size);
|
||||
else
|
||||
error("Font file '%s' in unrecognized format.", font_file.c_str());
|
||||
|
||||
if(font) {
|
||||
font->bold = bold;
|
||||
font->italic = italic;
|
||||
fonts[font_name] = font;
|
||||
font_list.push_back(font);
|
||||
|
||||
foreach(it, replaces)
|
||||
fonts[*it] = font;
|
||||
}
|
||||
}
|
||||
|
||||
font_name.clear();
|
||||
font_file.clear();
|
||||
font_locale.clear();
|
||||
replaces.clear();
|
||||
};
|
||||
|
||||
DataHandler datahandler;
|
||||
datahandler("Font", [&](std::string& value) {
|
||||
makeFont();
|
||||
font_name = value;
|
||||
bold = 0;
|
||||
italic = 0;
|
||||
pages.clear();
|
||||
size = 12;
|
||||
});
|
||||
|
||||
datahandler("File", [&](std::string& value) {
|
||||
font_file = devices.mods.resolve(value);
|
||||
});
|
||||
|
||||
datahandler("Locale", [&](std::string& value) {
|
||||
font_locale = value;
|
||||
});
|
||||
|
||||
datahandler("Replace", [&](std::string& value) {
|
||||
replaces.push_back(value);
|
||||
});
|
||||
|
||||
datahandler("Size", [&](std::string& value) {
|
||||
size = toNumber<int>(value);
|
||||
});
|
||||
|
||||
datahandler("Bold", [&](std::string& value) {
|
||||
auto it = fonts.find(value);
|
||||
if(it != fonts.end()) {
|
||||
bold = it->second;
|
||||
}
|
||||
else {
|
||||
error("Font '%s' does not exist.", value.c_str());
|
||||
}
|
||||
});
|
||||
|
||||
datahandler("Italic", [&](std::string& value) {
|
||||
auto it = fonts.find(value);
|
||||
if(it != fonts.end()) {
|
||||
italic = it->second;
|
||||
}
|
||||
else {
|
||||
error("Font '%s' does not exist.", value.c_str());
|
||||
}
|
||||
});
|
||||
|
||||
datahandler.defaultHandler([&](std::string& key, std::string& value) {
|
||||
if(key.compare(0, 4, "Page") == 0) {
|
||||
std::vector<std::string> numbers;
|
||||
split(value, numbers, '-');
|
||||
|
||||
if(numbers.size() == 2)
|
||||
pages.push_back(std::pair<int,int>(toNumber<int>(numbers[0], 0, std::hex),
|
||||
toNumber<int>(numbers[1], 255, std::hex)));
|
||||
}
|
||||
});
|
||||
|
||||
datahandler.read(filename);
|
||||
makeFont();
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,631 @@
|
||||
#include "main/references.h"
|
||||
#include "main/logging.h"
|
||||
#include "main/initialization.h"
|
||||
#include "render/render_state.h"
|
||||
#include "resource/library.h"
|
||||
#include "compat/misc.h"
|
||||
#include "str_util.h"
|
||||
#include "num_util.h"
|
||||
#include "threads.h"
|
||||
#include "files.h"
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <functional>
|
||||
#include <tuple>
|
||||
#include <queue>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "main/profiler.h"
|
||||
|
||||
extern bool cancelAssets;
|
||||
|
||||
namespace resource {
|
||||
|
||||
umap<std::string, render::SpriteMode> INIT_VAR(spriteModes) {
|
||||
spriteModes["Horizontal"] = render::SM_Horizontal;
|
||||
spriteModes["Vertical"] = render::SM_Vertical;
|
||||
} INIT_VAR_END;
|
||||
|
||||
umap<std::string, render::FaceCulling> INIT_VAR(cullingModes) {
|
||||
cullingModes["None"] = render::FC_None;
|
||||
cullingModes["Front"] = render::FC_Front;
|
||||
cullingModes["Back"] = render::FC_Back;
|
||||
cullingModes["Both"] = render::FC_Both;
|
||||
} INIT_VAR_END;
|
||||
|
||||
umap<std::string, render::DepthTest> INIT_VAR(depthTests) {
|
||||
depthTests["Never"] = render::DT_Never;
|
||||
depthTests["Less"] = render::DT_Less;
|
||||
depthTests["LessEqual"] = render::DT_LessEqual;
|
||||
depthTests["Equal"] = render::DT_Equal;
|
||||
depthTests["GreaterEqual"] = render::DT_GreaterEqual;
|
||||
depthTests["Greater"] = render::DT_Greater;
|
||||
depthTests["Always"] = render::DT_Always;
|
||||
depthTests["NoDepthTest"] = render::DT_NoDepthTest;
|
||||
} INIT_VAR_END;
|
||||
|
||||
umap<std::string, render::TextureWrap> INIT_VAR(textureWraps) {
|
||||
textureWraps["Repeat"] = render::TW_Repeat;
|
||||
textureWraps["Clamp"] = render::TW_Clamp;
|
||||
textureWraps["ClampEdge"] = render::TW_ClampEdge;
|
||||
textureWraps["Mirror"] = render::TW_Mirror;
|
||||
} INIT_VAR_END;
|
||||
|
||||
umap<std::string, render::TextureFilter> INIT_VAR(textureFilters) {
|
||||
textureFilters["Linear"] = render::TF_Linear;
|
||||
textureFilters["Nearest"] = render::TF_Nearest;
|
||||
} INIT_VAR_END;
|
||||
|
||||
umap<std::string, render::DrawMode> INIT_VAR(drawModes) {
|
||||
drawModes["Line"] = render::DM_Line;
|
||||
drawModes["Fill"] = render::DM_Fill;
|
||||
} INIT_VAR_END;
|
||||
|
||||
umap<std::string, render::BaseMaterial> INIT_VAR(baseMats) {
|
||||
baseMats["Solid"] = render::MAT_Solid;
|
||||
baseMats["Add"] = render::MAT_Add;
|
||||
baseMats["Alpha"] = render::MAT_Alpha;
|
||||
baseMats["Font"] = render::MAT_Font;
|
||||
baseMats["Overlay"] = render::MAT_Overlay;
|
||||
} INIT_VAR_END;
|
||||
|
||||
const unsigned DRIVER_MIPMAP_SIZE = 512 * 512;
|
||||
|
||||
struct QueuedTexture {
|
||||
int priority;
|
||||
std::vector<Image*> images;
|
||||
render::Texture* tex;
|
||||
std::string filename;
|
||||
bool mipmap;
|
||||
bool cachePixels;
|
||||
mutable unsigned row, lod;
|
||||
|
||||
QueuedTexture(render::Texture* dest, int Priority, Image* source, bool Mipmap, bool CachePixels)
|
||||
: priority(Priority), tex(dest), mipmap(Mipmap), cachePixels(CachePixels), row(0), lod(0)
|
||||
{
|
||||
images.push_back(source);
|
||||
}
|
||||
|
||||
QueuedTexture(render::Texture* dest, int Priority, const std::string& source, bool Mipmap, bool CachePixels)
|
||||
: priority(Priority), tex(dest), filename(source), mipmap(Mipmap), cachePixels(CachePixels), row(0), lod(0)
|
||||
{
|
||||
}
|
||||
|
||||
//Loads the image, returning the number of bytes loaded
|
||||
// If true was returned, the image is done loading or there was a failure
|
||||
// The number of bytes loaded may exceed maxBytes - it is only a soft limit
|
||||
bool load(unsigned maxBytes, unsigned& loadedBytes);
|
||||
|
||||
bool operator<(const QueuedTexture& other) const {
|
||||
return priority < other.priority;
|
||||
}
|
||||
|
||||
void clearImages() {
|
||||
for(auto i = images.begin(); i != images.end(); ++i)
|
||||
delete *i;
|
||||
images.clear();
|
||||
}
|
||||
};
|
||||
|
||||
threads::Mutex queuedImagesLock;
|
||||
std::priority_queue<QueuedTexture> queuedImages;
|
||||
|
||||
threads::Mutex queuedTexturesLock;
|
||||
std::priority_queue<QueuedTexture> queuedTextures;
|
||||
|
||||
threads::Mutex texIdentLock;
|
||||
std::unordered_map<std::string,render::Texture*> texNames;
|
||||
|
||||
unsigned maxTexSize = 0;
|
||||
|
||||
void Library::clearTextures() {
|
||||
texNames.clear();
|
||||
maxTexSize = 0;
|
||||
}
|
||||
|
||||
bool Library::hasQueuedImages() {
|
||||
return !queuedImages.empty();
|
||||
}
|
||||
|
||||
unsigned getMaxTextureSize() {
|
||||
if(maxTexSize == 0) {
|
||||
auto* texQuality = devices.settings.engine.getSetting("iTextureQuality");
|
||||
if(texQuality) {
|
||||
switch(texQuality->getInteger()) {
|
||||
case 0: maxTexSize = 256; break;
|
||||
case 1: maxTexSize = 512; break;
|
||||
case 2: maxTexSize = 1024; break;
|
||||
case 3: maxTexSize = 2048; break;
|
||||
case 4: maxTexSize = 4096; break;
|
||||
default:
|
||||
case 5: maxTexSize = 8192; break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
maxTexSize = 65536;
|
||||
}
|
||||
}
|
||||
|
||||
return maxTexSize * maxTexSize;
|
||||
}
|
||||
|
||||
bool Library::processImages(int maxPriority, int amount) {
|
||||
bool processedAny = false;
|
||||
int processed = 0;
|
||||
while(!queuedImages.empty()) {
|
||||
queuedImagesLock.lock();
|
||||
if(queuedImages.empty()) {
|
||||
queuedImagesLock.release();
|
||||
break;
|
||||
}
|
||||
|
||||
auto elem = queuedImages.top();
|
||||
if(elem.priority < maxPriority) {
|
||||
queuedImagesLock.release();
|
||||
break;
|
||||
}
|
||||
|
||||
if(elem.tex)
|
||||
textures.push_back(elem.tex);
|
||||
|
||||
queuedImages.pop();
|
||||
queuedImagesLock.release();
|
||||
|
||||
if(cancelAssets)
|
||||
continue;
|
||||
|
||||
processedAny = true;
|
||||
|
||||
if(!elem.filename.empty()) {
|
||||
Image* img = loadImage(elem.filename.c_str());
|
||||
|
||||
if(!img) {
|
||||
error("Error: Could not load image '%s'", elem.filename.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
unsigned texSize = getMaxTextureSize();
|
||||
while(img->width * img->height > texSize) {
|
||||
auto* prev = img;
|
||||
img = img->makeMipmap();
|
||||
delete prev;
|
||||
}
|
||||
|
||||
elem.images.push_back(img);
|
||||
|
||||
//Loading images this way causes weirdness
|
||||
/*if(elem.mipmap && img->width * img->height > DRIVER_MIPMAP_SIZE) {
|
||||
while(img->width > 2 && img->height > 2) {
|
||||
img = img->makeMipmap();
|
||||
if(img)
|
||||
elem.images.push_back(img);
|
||||
else
|
||||
break;
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
++processed;
|
||||
|
||||
queuedTexturesLock.lock();
|
||||
queuedTextures.push(elem);
|
||||
queuedTexturesLock.release();
|
||||
|
||||
if(processed >= amount)
|
||||
break;
|
||||
}
|
||||
|
||||
return processedAny;
|
||||
}
|
||||
|
||||
bool Library::hasQueuedTextures() {
|
||||
return !queuedTextures.empty();
|
||||
}
|
||||
|
||||
bool QueuedTexture::load(unsigned maxBytes, unsigned& loadedBytes) {
|
||||
if(images.empty())
|
||||
return true;
|
||||
if(!tex) {
|
||||
clearImages();
|
||||
return true;
|
||||
}
|
||||
|
||||
if(loadedBytes >= maxBytes)
|
||||
return false;
|
||||
|
||||
Image* img = images.front();
|
||||
unsigned rowBytes = img->width * ColorDepths[img->format];
|
||||
unsigned loadRows = (maxBytes - loadedBytes) / rowBytes;
|
||||
if(loadRows == 0)
|
||||
loadRows = 1;
|
||||
if(loadRows > img->height - row)
|
||||
loadRows = img->height - row;
|
||||
loadedBytes += rowBytes * loadRows;
|
||||
|
||||
if(lod == 0/* && loadRows == img->height && img->width * img->height <= DRIVER_MIPMAP_SIZE*/) {
|
||||
tex->load(*img, mipmap, cachePixels);
|
||||
clearImages();
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
if(row == 0)
|
||||
tex->loadStart(*img, mipmap, cachePixels, lod);
|
||||
tex->loadPartial(*img, recti(0, row, img->width, row+loadRows), cachePixels, lod);
|
||||
row += loadRows;
|
||||
|
||||
if(row == img->height) {
|
||||
delete img;
|
||||
images.erase(images.begin());
|
||||
|
||||
tex->loadFinish(mipmap && images.empty(), lod);
|
||||
if(!mipmap || images.empty())
|
||||
return true;
|
||||
|
||||
//Reset for the next LOD
|
||||
lod += 1;
|
||||
row = 0;
|
||||
return load(maxBytes, loadedBytes);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Library::processTextures(int maxPriority, bool singleFrame) {
|
||||
bool processedAny = false;
|
||||
const unsigned frameByteLimit = 1024 * 1024 * 4;
|
||||
int64_t byteLimit = singleFrame ? frameByteLimit : 0xffffffff;
|
||||
|
||||
while(!queuedTextures.empty()) {
|
||||
queuedTexturesLock.lock();
|
||||
if(queuedTextures.empty()) {
|
||||
queuedTexturesLock.release();
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
auto tex = queuedTextures.top();
|
||||
if(tex.priority < maxPriority) {
|
||||
queuedTexturesLock.release();
|
||||
break;
|
||||
}
|
||||
|
||||
queuedTextures.pop();
|
||||
|
||||
//Track filenames even if we can't load the resource at the moment, in case the file is created while we're running
|
||||
if(tex.tex && !tex.filename.empty())
|
||||
texture_files[tex.filename] = tex.tex;
|
||||
|
||||
queuedTexturesLock.release();
|
||||
|
||||
if(cancelAssets)
|
||||
continue;
|
||||
|
||||
processedAny = true;
|
||||
|
||||
unsigned loaded = 0;
|
||||
if(!tex.load(byteLimit, loaded)) {
|
||||
threads::Lock lock(queuedTexturesLock);
|
||||
queuedTextures.push(tex);
|
||||
}
|
||||
|
||||
byteLimit -= (int64_t)loaded;
|
||||
|
||||
if(byteLimit <= 0)
|
||||
break;
|
||||
}
|
||||
|
||||
return processedAny;
|
||||
}
|
||||
|
||||
render::Texture* queueImage(const std::string& abs_file, int priority = -20, bool mipmap = true, bool cachePixels = false, bool cubemap = false) {
|
||||
bool queue = false;
|
||||
render::Texture* tex;
|
||||
{
|
||||
texIdentLock.lock();
|
||||
render::Texture*& pTex = texNames[abs_file];
|
||||
if(pTex == 0) {
|
||||
if(cubemap)
|
||||
pTex = render::RenderDriver::createCubemap();
|
||||
else
|
||||
pTex = render::RenderDriver::createTexture();
|
||||
queue = true;
|
||||
}
|
||||
tex = pTex;
|
||||
texIdentLock.release();
|
||||
}
|
||||
|
||||
if(queue) {
|
||||
queuedImagesLock.lock();
|
||||
resource::QueuedTexture queued = resource::QueuedTexture(tex, priority, abs_file, mipmap, cachePixels);
|
||||
queuedImages.push(queued);
|
||||
queuedImagesLock.release();
|
||||
}
|
||||
|
||||
return tex;
|
||||
}
|
||||
|
||||
render::Texture* queueImage(Image* img, int priority, bool mipmap, bool cachePixels) {
|
||||
render::Texture* tex = render::RenderDriver::createTexture();
|
||||
|
||||
queuedTexturesLock.lock();
|
||||
resource::QueuedTexture queued = resource::QueuedTexture(tex, priority, img, mipmap, cachePixels);
|
||||
queuedTextures.push(queued);
|
||||
queuedTexturesLock.release();
|
||||
|
||||
return tex;
|
||||
}
|
||||
|
||||
void queueTextureUpdate(render::Texture* tex, Image* img, int priority, bool mipmap, bool cachePixels) {
|
||||
queuedTexturesLock.lock();
|
||||
resource::QueuedTexture queued = resource::QueuedTexture(tex, priority, img, mipmap, cachePixels);
|
||||
queuedTextures.push(queued);
|
||||
queuedTexturesLock.release();
|
||||
}
|
||||
|
||||
void Library::loadMaterials(const std::string& filename) {
|
||||
DataHandler datahandler;
|
||||
|
||||
render::RenderState* state = nullptr;
|
||||
render::SpriteSheet* sheet = nullptr;
|
||||
render::MaterialGroup* group = nullptr;
|
||||
std::string matName;
|
||||
int priority = -61;
|
||||
bool texdefs = false;
|
||||
|
||||
//Initialization handling
|
||||
datahandler("SpriteSheet", [&](std::string& value) {
|
||||
matName = value;
|
||||
priority = -61;
|
||||
group = nullptr;
|
||||
sheet = new render::SpriteSheet();
|
||||
state = &sheet->material;
|
||||
spritesheets[matName] = sheet;
|
||||
sheet_indices[sheet] = (unsigned)spritesheet_names.size();
|
||||
spritesheet_names.push_back(matName);
|
||||
texdefs = false;
|
||||
});
|
||||
|
||||
datahandler("Material", [&](std::string& value) {
|
||||
matName = value;
|
||||
sheet = 0;
|
||||
group = nullptr;
|
||||
priority = -61;
|
||||
if(materials.find(matName) != materials.end()) {
|
||||
error("Duplicate material: %s", matName.c_str());
|
||||
state = materials[matName];
|
||||
*state = render::RenderState();
|
||||
}
|
||||
else {
|
||||
state = new render::RenderState();
|
||||
state->constant = true;
|
||||
}
|
||||
|
||||
materials[matName] = state;
|
||||
mat_indices[state] = (unsigned)material_names.size();
|
||||
material_names.push_back(matName);
|
||||
texdefs = false;
|
||||
});
|
||||
|
||||
datahandler("MaterialGroup", [&](std::string& value) {
|
||||
matName = value;
|
||||
sheet = nullptr;
|
||||
state = nullptr;
|
||||
group = new render::MaterialGroup();
|
||||
group->prefix = value + "_";
|
||||
matGroups[value] = group;
|
||||
});
|
||||
|
||||
datahandler("Template", [&](std::string& value) {
|
||||
if(!group)
|
||||
return;
|
||||
|
||||
auto it = materials.find(value);
|
||||
if(it != materials.end())
|
||||
group->base = *it->second;
|
||||
else
|
||||
error("Could not find template material '%s' for '%s'", value.c_str(), matName.c_str());
|
||||
});
|
||||
|
||||
datahandler("Prefix", [&](std::string& value) {
|
||||
if(!group)
|
||||
return;
|
||||
|
||||
group->prefix = value;
|
||||
});
|
||||
|
||||
datahandler("Folder", [&](std::string& value) {
|
||||
if(!group)
|
||||
return;
|
||||
|
||||
auto& g = *group;
|
||||
auto& mats = materials;
|
||||
auto& inds = mat_indices;
|
||||
auto& names = material_names;
|
||||
int texPriority = priority;
|
||||
|
||||
devices.mods.listFiles(value, "*.png", [&](const std::string& filename) {
|
||||
std::string id = g.prefix + getBasename(filename, false);
|
||||
makeIdentifier(id);
|
||||
auto* mat = new render::RenderState();
|
||||
|
||||
*mat = g.base;
|
||||
mat->constant = true;
|
||||
|
||||
mats[id] = mat;
|
||||
inds[mat] = (unsigned)names.size();
|
||||
names.push_back(id);
|
||||
|
||||
g.names.push_back(id);
|
||||
g.materials.push_back(mat);
|
||||
|
||||
if(load_resources) {
|
||||
mat->textures[0] = queueImage(filename, texPriority, mat->mipmap, mat->cachePixels);
|
||||
if(watch_resources)
|
||||
devices.library.watchTexture(filename);
|
||||
}
|
||||
}, true);
|
||||
});
|
||||
|
||||
datahandler("Inherit", [&](std::string& value) {
|
||||
if(!state)
|
||||
return;
|
||||
|
||||
auto it = materials.find(value);
|
||||
if(it != materials.end())
|
||||
*state = *it->second;
|
||||
else
|
||||
error("Could not find material '%s' to inherit for '%s'", value.c_str(), matName.c_str());
|
||||
});
|
||||
|
||||
//Renderstate members
|
||||
HANDLE_BOOL(datahandler, "DepthWrite", state, depthWrite);
|
||||
HANDLE_ENUM(datahandler, "DepthTest", state, depthTest, depthTests);
|
||||
HANDLE_ENUM(datahandler, "Culling", state, culling, cullingModes);
|
||||
HANDLE_BOOL(datahandler, "Lighting", state, lighting);
|
||||
HANDLE_BOOL(datahandler, "NormalizeNormals", state, normalizeNormals);
|
||||
HANDLE_ENUM_W(datahandler, "Shader", state, shader, shaders, load_resources);
|
||||
HANDLE_NUM(datahandler, "Shininess", state, shininess);
|
||||
HANDLE_ENUM(datahandler, "WrapVertical", state, wrapVertical, textureWraps);
|
||||
HANDLE_ENUM(datahandler, "WrapHorizontal", state, wrapHorizontal, textureWraps);
|
||||
HANDLE_ENUM(datahandler, "FilterMin", state, filterMin, textureFilters);
|
||||
HANDLE_ENUM(datahandler, "FilterMag", state, filterMag, textureFilters);
|
||||
HANDLE_ENUM(datahandler, "DrawMode", state, drawMode, drawModes);
|
||||
HANDLE_ENUM(datahandler, "Blend", state, baseMat, baseMats);
|
||||
|
||||
datahandler("Mipmap", [&](std::string& value) {
|
||||
if(!state)
|
||||
return;
|
||||
if(texdefs) {
|
||||
warn("Warning: Mipmap statement should be before texture"
|
||||
" declarations.\n %s", datahandler.position().c_str());
|
||||
}
|
||||
state->mipmap = toBool(value);
|
||||
});
|
||||
|
||||
datahandler("CachePixels", [&](std::string& value) {
|
||||
if(!state)
|
||||
return;
|
||||
state->cachePixels = toBool(value);
|
||||
});
|
||||
|
||||
datahandler("LoadPriority", [&](std::string& value) {
|
||||
if(value == "Critical" || value == "Menu")
|
||||
priority = 10;
|
||||
else if(value == "Game")
|
||||
priority = -10;
|
||||
else if(value == "High")
|
||||
priority = -31;
|
||||
else if(value == "Low")
|
||||
priority = -91;
|
||||
else
|
||||
priority = -111 + toNumber<int>(value);
|
||||
});
|
||||
|
||||
datahandler("Alpha", [&](std::string& value) {
|
||||
if(!state)
|
||||
return;
|
||||
|
||||
if(toBool(value))
|
||||
state->baseMat = render::MAT_Alpha;
|
||||
else
|
||||
state->baseMat = render::MAT_Solid;
|
||||
});
|
||||
|
||||
datahandler("Diffuse", [&](std::string& value) {
|
||||
int r,g,b,a;
|
||||
if(int args = sscanf(value.c_str(),"#%2x%2x%2x%2x", &r,&g,&b,&a)) {
|
||||
if(args == 3)
|
||||
state->diffuse = Colorf(Color(r,g,b));
|
||||
else if(args == 4)
|
||||
state->diffuse = Colorf(Color(r,g,b,a));
|
||||
}
|
||||
else {
|
||||
sscanf(value.c_str(), "%f,%f,%f,%f", &state->diffuse.r, &state->diffuse.g, &state->diffuse.b, &state->diffuse.a);
|
||||
}
|
||||
});
|
||||
|
||||
datahandler("Specular", [&](std::string& value) {
|
||||
int r,g,b,a;
|
||||
if(int args = sscanf(value.c_str(),"#%2x%2x%2x%2x", &r,&g,&b,&a)) {
|
||||
if(args == 3)
|
||||
state->specular = Colorf(Color(r,g,b));
|
||||
else if(args == 4)
|
||||
state->specular = Colorf(Color(r,g,b,a));
|
||||
}
|
||||
else {
|
||||
sscanf(value.c_str(), "%f,%f,%f,%f", &state->specular.r, &state->specular.g, &state->specular.b, &state->specular.a);
|
||||
}
|
||||
});
|
||||
|
||||
//Spritesheet members
|
||||
HANDLE_ENUM(datahandler, "Mode", sheet, mode, spriteModes);
|
||||
|
||||
datahandler("Size", [&](std::string& value) {
|
||||
if(!sheet)
|
||||
return;
|
||||
|
||||
std::vector<std::string> args;
|
||||
split(value, args, ',');
|
||||
|
||||
if(args.size() != 2)
|
||||
return;
|
||||
|
||||
sheet->width = toNumber<int>(args[0]);
|
||||
sheet->height = toNumber<int>(args[1]);
|
||||
});
|
||||
|
||||
datahandler("Spacing", [&](std::string& value) {
|
||||
if(!sheet)
|
||||
return;
|
||||
|
||||
sheet->spacing = toNumber<int>(value);
|
||||
});
|
||||
|
||||
datahandler.defaultHandler([&](std::string& key, std::string& value) {
|
||||
if(key.size() >= 7 && (key.compare(0, 7, "Texture") == 0 || key.compare(0, 7, "Cubemap") == 0)) {
|
||||
//Figure out the texture number to set
|
||||
int texNum = 0;
|
||||
texdefs = true;
|
||||
if (key.size() > 7) {
|
||||
std::string num = key.substr(7, key.size() - 7);
|
||||
texNum = min_(toNumber<int>(num)-1, RENDER_MAX_TEXTURES);
|
||||
}
|
||||
|
||||
value = getAbsolutePath( devices.mods.resolve(value) );
|
||||
if(load_resources) {
|
||||
state->textures[texNum] = queueImage(value, priority, state->mipmap, state->cachePixels, key[0] == 'C');
|
||||
if(watch_resources)
|
||||
watchTexture(value);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
datahandler.read(filename);
|
||||
}
|
||||
|
||||
std::string Library::getSpriteDesc(const render::Sprite& sprt) const {
|
||||
std::string ret;
|
||||
if(sprt.mat) {
|
||||
auto it = mat_indices.find(sprt.mat);
|
||||
if(it != mat_indices.end())
|
||||
ret = material_names[it->second];
|
||||
}
|
||||
else {
|
||||
auto it = sheet_indices.find(sprt.sheet);
|
||||
if(it == sheet_indices.end())
|
||||
return ret;
|
||||
ret += spritesheet_names[it->second];
|
||||
ret += "::";
|
||||
ret += toString(sprt.index);
|
||||
}
|
||||
if(sprt.color.color != 0xffffffff) {
|
||||
ret += "*";
|
||||
ret += toString(sprt.color);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,246 @@
|
||||
#include "main/references.h"
|
||||
#include "main/initialization.h"
|
||||
#include "main/logging.h"
|
||||
#include "render/render_mesh.h"
|
||||
#include "resource/library.h"
|
||||
#include "str_util.h"
|
||||
#include "num_util.h"
|
||||
#include "util/mesh_generation.h"
|
||||
#include "threads.h"
|
||||
#include "files.h"
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
#include "render/x_loader.h"
|
||||
#include "render/obj_loader.h"
|
||||
#include "render/bmf_loader.h"
|
||||
#include "render/ogex_loader.h"
|
||||
|
||||
bool useModelCache = false;
|
||||
extern bool cancelAssets;
|
||||
|
||||
namespace resource {
|
||||
|
||||
|
||||
threads::Signal unqueuedMeshes;
|
||||
|
||||
threads::Mutex queuedMeshLock;
|
||||
std::vector<std::tuple<Mesh*,render::RenderMesh*>> queuedMeshes;
|
||||
|
||||
bool Library::hasQueuedMeshes() {
|
||||
return !unqueuedMeshes.check(0) || !queuedMeshes.empty();
|
||||
}
|
||||
|
||||
bool Library::processMeshes(int maxPriority, int amount) {
|
||||
if(queuedMeshes.empty())
|
||||
return false;
|
||||
|
||||
//TODO: Probably this has a race condition (threads end while this loop is running)
|
||||
queuedMeshLock.lock();
|
||||
while(!queuedMeshes.empty()) {
|
||||
auto& pair = queuedMeshes.back();
|
||||
std::get<1>(pair)->resetToMesh( *std::get<0>(pair) );
|
||||
delete std::get<0>(pair);
|
||||
queuedMeshes.pop_back();
|
||||
}
|
||||
queuedMeshLock.release();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
struct MeshLoadData {
|
||||
bool isSphere;
|
||||
bool makeTangents;
|
||||
std::string fileName;
|
||||
unsigned width, height;
|
||||
|
||||
render::RenderMesh* rMesh;
|
||||
const render::RenderMesh* lodMesh;
|
||||
double lodDist;
|
||||
|
||||
MeshLoadData() : isSphere(false), makeTangents(false), width(0), height(0), lodMesh(0), lodDist(1) { }
|
||||
};
|
||||
|
||||
threads::atomic_int activeMeshThreads;
|
||||
|
||||
threads::threadreturn threadcall LoadMesh(void* loadData) {
|
||||
MeshLoadData& data = *(MeshLoadData*)loadData;
|
||||
|
||||
Mesh* mesh = 0;
|
||||
|
||||
while(++activeMeshThreads > 4) {
|
||||
--activeMeshThreads;
|
||||
threads::sleep(5);
|
||||
}
|
||||
|
||||
if(cancelAssets) {
|
||||
--activeMeshThreads;
|
||||
unqueuedMeshes.signalDown();
|
||||
delete &data;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
//Create raw mesh
|
||||
if(data.isSphere) {
|
||||
mesh = generateSphereMesh(data.height, data.width);
|
||||
}
|
||||
else {
|
||||
mesh = new Mesh();
|
||||
//TODO: Make work if multiple meshes happen to have the same name, especially on Windows
|
||||
std::string cacheModel = devices.mods.getProfile("model_cache") + "/" + getBasename(data.fileName);
|
||||
std::string sourceModel = devices.mods.resolve(data.fileName);
|
||||
|
||||
if(useModelCache && fileExists(cacheModel) && (getModifiedTime(cacheModel) - getModifiedTime(sourceModel)) >= 0) {
|
||||
render::loadBinaryMesh(cacheModel.c_str(), *mesh);
|
||||
if(mesh->faces.empty())
|
||||
goto failedCache;
|
||||
}
|
||||
else {
|
||||
failedCache:
|
||||
if(fileExists(sourceModel)) {
|
||||
if(match(sourceModel.c_str(), ".x"))
|
||||
render::loadMeshX(sourceModel.c_str(), *mesh);
|
||||
else if(match(sourceModel.c_str(), ".ogex"))
|
||||
render::loadMeshOGEX(sourceModel.c_str(), *mesh);
|
||||
else
|
||||
render::loadMeshOBJ(sourceModel.c_str(), *mesh);
|
||||
|
||||
if(!mesh->faces.empty() && useModelCache)
|
||||
render::saveBinaryMesh(cacheModel.c_str(), *mesh);
|
||||
}
|
||||
else {
|
||||
error("Could not find model file '%s'", sourceModel.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!mesh || mesh->faces.empty())
|
||||
error("Could not load mesh '%s'", data.fileName.c_str());
|
||||
|
||||
//Queue mesh for the main thread to generate the GL mesh
|
||||
if(mesh) {
|
||||
//Calculate tangents and binormals
|
||||
if(data.makeTangents) {
|
||||
mesh->tangents.resize(mesh->vertices.size());
|
||||
std::vector<vec3f> binormals(mesh->vertices.size());
|
||||
|
||||
for(auto i = mesh->faces.begin(), end = mesh->faces.end(); i != end; ++i) {
|
||||
auto& face = *i;
|
||||
|
||||
auto& a = mesh->vertices[face.a];
|
||||
auto& b = mesh->vertices[face.b];
|
||||
auto& c = mesh->vertices[face.c];
|
||||
|
||||
vec3f d1 = b.position - a.position;
|
||||
vec3f d2 = c.position - a.position;
|
||||
|
||||
vec2f s = vec2f(b.u - a.u, c.u - a.u);
|
||||
vec2f t = vec2f(b.v - a.v, c.v - a.v);
|
||||
|
||||
float r = (s.x * t.y - s.y * t.x);
|
||||
if(r < 0.0001f && r > -0.0001f)
|
||||
continue;
|
||||
r = 1.f / r;
|
||||
vec3f bnDir = ((d2 * s.x) - (d1 * s.y)) * r;
|
||||
vec3f td = ((d1 * t.y) - (d2 * t.x)) * r;
|
||||
|
||||
vec4f tanDir = vec4f(td.x, td.y, td.z, 0.f);
|
||||
|
||||
mesh->tangents[face.a] += tanDir; if(mesh->tangents[face.a].zero()) mesh->tangents[face.a] = tanDir;
|
||||
mesh->tangents[face.b] += tanDir; if(mesh->tangents[face.b].zero()) mesh->tangents[face.b] = tanDir;
|
||||
mesh->tangents[face.c] += tanDir; if(mesh->tangents[face.c].zero()) mesh->tangents[face.c] = tanDir;
|
||||
|
||||
binormals[face.a] += bnDir; if(binormals[face.a].zero()) binormals[face.a] = bnDir;
|
||||
binormals[face.b] += bnDir; if(binormals[face.b].zero()) binormals[face.b] = bnDir;
|
||||
binormals[face.c] += bnDir; if(binormals[face.c].zero()) binormals[face.c] = bnDir;
|
||||
}
|
||||
|
||||
for(unsigned i = 0; i < mesh->vertices.size(); ++i) {
|
||||
auto& vertex = mesh->vertices[i];
|
||||
auto& tangent = mesh->tangents[i];
|
||||
|
||||
vec3f t = vec3f(tangent.x, tangent.y, tangent.z);
|
||||
|
||||
bool handedness = (vertex.normal.cross(t).dot(binormals[i]) > 0.f);
|
||||
|
||||
//Restrict to tangent plane
|
||||
t = (t - vertex.normal * vertex.normal.dot(t)).normalized();
|
||||
|
||||
tangent.x = t.x;
|
||||
tangent.y = t.y;
|
||||
tangent.z = t.z;
|
||||
tangent.w = handedness ? 1.f : -1.f;
|
||||
}
|
||||
}
|
||||
|
||||
if(!mesh->colors.empty() && mesh->colors.size() < mesh->vertices.size())
|
||||
mesh->colors.resize(mesh->vertices.size());
|
||||
|
||||
//TODO: Handle an invalid mesh being loaded (the referenced mesh must be valid, but we have no data to load in)
|
||||
if(data.lodMesh)
|
||||
data.rMesh->setLOD(data.lodDist, data.lodMesh);
|
||||
queuedMeshLock.lock();
|
||||
queuedMeshes.push_back(std::tuple<Mesh*,render::RenderMesh*>(mesh,data.rMesh));
|
||||
queuedMeshLock.release();
|
||||
}
|
||||
|
||||
--activeMeshThreads;
|
||||
|
||||
unqueuedMeshes.signalDown();
|
||||
delete &data;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Library::loadModels(const std::string& filename) {
|
||||
MeshLoadData* meshData = 0;
|
||||
|
||||
DataHandler datahandler;
|
||||
|
||||
auto makeModel = [&](bool final) {
|
||||
if(load_resources && meshData) {
|
||||
unqueuedMeshes.signalUp();
|
||||
threads::createThread(LoadMesh,meshData);
|
||||
}
|
||||
|
||||
if(!final)
|
||||
meshData = new MeshLoadData();
|
||||
};
|
||||
|
||||
datahandler("Model", [&](std::string& value) {
|
||||
makeModel(false);
|
||||
if(load_resources)
|
||||
meshData->rMesh = devices.render->createMesh(Mesh());
|
||||
else
|
||||
meshData->rMesh = errors.mesh;
|
||||
|
||||
meshes[value] = meshData->rMesh;
|
||||
});
|
||||
|
||||
datahandler("Mesh", [&](std::string& value) {
|
||||
meshData->fileName = value;
|
||||
});
|
||||
|
||||
datahandler("Tangents", [&](std::string& value) {
|
||||
meshData->makeTangents = toBool(value, true);
|
||||
});
|
||||
|
||||
datahandler("Sphere", [&](std::string& value) {
|
||||
if(sscanf(value.c_str(), "%d x %d", &meshData->width, &meshData->height) == 2 && meshData->width > 0 && meshData->height > 0 && meshData->width*meshData->height < 256*256)
|
||||
meshData->isSphere = true;
|
||||
});
|
||||
|
||||
datahandler("LOD", [&](std::string& value) {
|
||||
char name[256];
|
||||
|
||||
if(sscanf(value.c_str(), " %255s > %lf", name, &meshData->lodDist) == 2)
|
||||
meshData->lodMesh = &getMesh(name);
|
||||
});
|
||||
|
||||
datahandler.read(filename);
|
||||
makeModel(true);
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,679 @@
|
||||
#include "main/references.h"
|
||||
#include "main/initialization.h"
|
||||
#include "main/logging.h"
|
||||
#include "render/render_state.h"
|
||||
#include "render/shader.h"
|
||||
#include "resource/library.h"
|
||||
#include "str_util.h"
|
||||
#include "num_util.h"
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include "util/random.h"
|
||||
|
||||
#include "files.h"
|
||||
#include <set>
|
||||
|
||||
//External shader uniform handlers
|
||||
extern void shader_frameTime(float*,unsigned short,void*);
|
||||
extern void shader_gameTime(float*,unsigned short,void*);
|
||||
extern void shader_frameTime_cycle(float*,unsigned short,void*);
|
||||
extern void shader_gameTime_cycle(float*,unsigned short,void*);
|
||||
extern void shader_frameTime_cycle_abs(float*,unsigned short,void*);
|
||||
extern void shader_gameTime_cycle_abs(float*,unsigned short,void*);
|
||||
extern void shader_pixelRatio(float*,unsigned short,void*);
|
||||
extern void shader_sprite_pos(float*,unsigned short,void*);
|
||||
|
||||
extern void shader_skin_margin_src(float*,unsigned short,void*);
|
||||
extern void shader_skin_margin_dest(float*,unsigned short,void*);
|
||||
extern void shader_skin_src_pos(float*,unsigned short,void*);
|
||||
extern void shader_skin_src_size(float*,unsigned short,void*);
|
||||
extern void shader_skin_dst_size(float*,unsigned short,void*);
|
||||
extern void shader_skin_mode(float*,unsigned short,void*);
|
||||
extern void shader_skin_gradientCount(float*,unsigned short,void*);
|
||||
extern void shader_skin_gradientMode(float*,unsigned short,void*);
|
||||
extern void shader_skin_gradientRects(float*,unsigned short,void*);
|
||||
extern void shader_skin_gradientCols(float*,unsigned short,void*);
|
||||
|
||||
extern void shader_unique(float* values,unsigned short,void*);
|
||||
extern void shader_node_color(float*,unsigned short,void*);
|
||||
extern void shader_node_distance(float*,unsigned short,void*);
|
||||
extern void shader_node_scale(float*,unsigned short,void*);
|
||||
extern void shader_node_selected(float*,unsigned short,void*);
|
||||
extern void shader_emp_flag(int*,unsigned short,void*);
|
||||
extern void shader_obj_velocity(float*,unsigned short,void*);
|
||||
extern void shader_obj_position(float*,unsigned short,void*);
|
||||
extern void shader_obj_rotation(float*,unsigned short,void*);
|
||||
extern void shader_node_position(float*,unsigned short,void*);
|
||||
extern void shader_node_rotation(float*,unsigned short,void*);
|
||||
extern void shader_obj_id(int*,unsigned short,void*);
|
||||
extern void shader_obj_acceleration(float*,unsigned short,void*);
|
||||
|
||||
extern void shader_plane_minrad(float*,unsigned short,void*);
|
||||
extern void shader_plane_maxrad(float*,unsigned short,void*);
|
||||
|
||||
extern void shader_tex_size(float*,unsigned short,void*);
|
||||
extern void shader_light_radius(float*,unsigned short,void*);
|
||||
extern void shader_light_position(float*,unsigned short,void*);
|
||||
extern void shader_light_screen(float*,unsigned short,void*);
|
||||
extern void shader_light_active(float*,unsigned short,void*);
|
||||
|
||||
extern unsigned registerVar(const std::string& name);
|
||||
extern void shader_statevars(float*,unsigned short,void*);
|
||||
|
||||
extern void shader_quadrant_damage(float*,unsigned short,void*);
|
||||
|
||||
void shader_invView(float* mat,unsigned short,void*) {
|
||||
devices.render->getInverseView(mat);
|
||||
}
|
||||
|
||||
void shader_random(float* values,unsigned short,void*) {
|
||||
values[0] = randomf();
|
||||
}
|
||||
|
||||
namespace resource {
|
||||
|
||||
ShaderGlobal::ShaderGlobal() : type(render::Shader::VT_invalid), size(0), ptr(0), arraySize(1) {
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, ShaderGlobal*> shaderGlobals;
|
||||
|
||||
void shader_global(float* out, unsigned short n, void* args) {
|
||||
ShaderGlobal* glob = (ShaderGlobal*)args;
|
||||
memcpy(out, glob->ptr, glob->size);
|
||||
}
|
||||
|
||||
void loadToInts(int* ints, std::vector<std::string>::iterator from, std::vector<std::string>::iterator to) {
|
||||
for(; from != to; ++from, ++ints)
|
||||
*ints = atoi(from->c_str());
|
||||
}
|
||||
|
||||
void loadToInt2s(int* ints, std::vector<std::string>::iterator from, std::vector<std::string>::iterator to) {
|
||||
for(; from != to; ++from, ints += 2)
|
||||
sscanf(from->c_str(),"<%d,%d>",ints,ints+1);
|
||||
}
|
||||
|
||||
void loadToInt3s(int* ints, std::vector<std::string>::iterator from, std::vector<std::string>::iterator to) {
|
||||
for(; from != to; ++from, ints += 3)
|
||||
sscanf(from->c_str(),"<%d,%d,%d>",ints,ints+1,ints+2);
|
||||
}
|
||||
|
||||
void loadToInt4s(int* ints, std::vector<std::string>::iterator from, std::vector<std::string>::iterator to) {
|
||||
for(; from != to; ++from, ints += 4)
|
||||
sscanf(from->c_str(),"<%d,%d,%d,%d>",ints,ints+1,ints+2,ints+3);
|
||||
}
|
||||
|
||||
void loadToFloats(float* floats, std::vector<std::string>::iterator from, std::vector<std::string>::iterator to) {
|
||||
for(; from != to; ++from, ++floats)
|
||||
*floats = (float)atof(from->c_str());
|
||||
}
|
||||
|
||||
void loadToFloat2s(float* floats, std::vector<std::string>::iterator from, std::vector<std::string>::iterator to) {
|
||||
for(; from != to; ++from, floats += 2)
|
||||
sscanf(from->c_str(),"<%f,%f>",floats,floats+1);
|
||||
}
|
||||
|
||||
void loadToFloat3s(float* floats, std::vector<std::string>::iterator from, std::vector<std::string>::iterator to) {
|
||||
for(; from != to; ++from, floats += 3)
|
||||
sscanf(from->c_str(),"<%f,%f,%f>",floats,floats+1,floats+2);
|
||||
}
|
||||
|
||||
void loadToFloat4s(float* floats, std::vector<std::string>::iterator from, std::vector<std::string>::iterator to) {
|
||||
for(; from != to; ++from, floats += 4)
|
||||
sscanf(from->c_str(),"<%f,%f,%f,%f>",floats,floats+1,floats+2,floats+3);
|
||||
}
|
||||
|
||||
void Library::compileShaders() {
|
||||
foreach(program,programs) {
|
||||
int result = program->second->compile();
|
||||
if(result != 0)
|
||||
error("-In Shader Program '%s'", program->first.c_str());
|
||||
}
|
||||
|
||||
foreach(shader,shaders) {
|
||||
int result = shader->second->compile();
|
||||
if(result != 0)
|
||||
error("-In Shader '%s'", shader->first.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void Library::clearShaderGlobals() {
|
||||
foreach(it, shaderGlobals) {
|
||||
free(it->second->ptr);
|
||||
delete it->second;
|
||||
}
|
||||
shaderGlobals.clear();
|
||||
}
|
||||
|
||||
void Library::iterateShaderGlobals(std::function<void(std::string&,resource::ShaderGlobal*)> func) {
|
||||
foreach(it, shaderGlobals)
|
||||
func(it->second->name, it->second);
|
||||
}
|
||||
|
||||
void Library::loadShaders(const std::string& filename) {
|
||||
render::Shader* shader = 0;
|
||||
|
||||
std::vector<render::Shader::Variable> vars;
|
||||
std::string vertex_file;
|
||||
std::string fragment_file;
|
||||
|
||||
DataHandler datahandler;
|
||||
|
||||
bool activeBlock = true, anyBlockActive = false;
|
||||
|
||||
int shaderLevel = 3;
|
||||
auto* sl = devices.settings.engine.getSetting("iShaderLevel");
|
||||
if(sl)
|
||||
shaderLevel = sl->getInteger();
|
||||
|
||||
auto BuildShader = [&]() {
|
||||
if(!shader)
|
||||
return;
|
||||
|
||||
auto progName = vertex_file + "|" + fragment_file;
|
||||
auto p = programs.find(progName);
|
||||
if(p != programs.end()) {
|
||||
shader->program = p->second;
|
||||
return;
|
||||
}
|
||||
|
||||
render::ShaderProgram* program = 0;
|
||||
|
||||
if(load_resources)
|
||||
program = devices.render->createShaderProgram(vertex_file.c_str(), fragment_file.c_str());
|
||||
|
||||
shader->program = program;
|
||||
programs[progName] = program;
|
||||
|
||||
if(watch_resources) {
|
||||
watchShader(progName, vertex_file);
|
||||
watchShader(progName, fragment_file);
|
||||
}
|
||||
|
||||
fragment_file.clear();
|
||||
vertex_file.clear();
|
||||
};
|
||||
|
||||
datahandler.controlHandler([&](std::string& line) -> bool {
|
||||
if(!line.empty() && line[0] == '#') {
|
||||
if(line.compare(0, 6, "#endif") == 0) {
|
||||
activeBlock = true;
|
||||
anyBlockActive = false;
|
||||
return false;
|
||||
}
|
||||
else if(line.compare(0, 5, "#else") == 0) {
|
||||
activeBlock = !anyBlockActive;
|
||||
anyBlockActive = true;
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
std::string control, condition;
|
||||
if(splitKeyValue(line, control, condition, " ")) {
|
||||
if(control == "#if" || control == "#elif") {
|
||||
if(anyBlockActive) {
|
||||
activeBlock = false;
|
||||
}
|
||||
else {
|
||||
bool enterBlock = false;
|
||||
if(condition == "fallback") {
|
||||
auto* fallback = devices.settings.engine.getSetting("bShaderFallback");
|
||||
if(fallback)
|
||||
enterBlock = *fallback;
|
||||
}
|
||||
else if(condition == "low") {
|
||||
enterBlock = shaderLevel == 1;
|
||||
}
|
||||
else if(condition == "medium") {
|
||||
enterBlock = shaderLevel == 2;
|
||||
}
|
||||
else if(condition == "high") {
|
||||
enterBlock = shaderLevel == 3;
|
||||
}
|
||||
else if(condition == "extreme") {
|
||||
enterBlock = shaderLevel == 4;
|
||||
}
|
||||
else {
|
||||
activeBlock = toBool(condition);
|
||||
}
|
||||
activeBlock = enterBlock;
|
||||
anyBlockActive = activeBlock;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
error("Unrecognized directive %s", line.c_str());
|
||||
return false;
|
||||
}
|
||||
return activeBlock;
|
||||
});
|
||||
|
||||
datahandler("Shader", [&](std::string& value) {
|
||||
fragment_file.clear();
|
||||
vertex_file.clear();
|
||||
|
||||
if(shaders.find(value) != shaders.end()) {
|
||||
shader = 0;
|
||||
error("Duplicate shader entry '%s'", value.c_str());
|
||||
return;
|
||||
}
|
||||
shader = devices.render->createShader();
|
||||
shaders[value] = shader;
|
||||
});
|
||||
|
||||
datahandler("Vertex", [&](std::string& value) {
|
||||
vertex_file = devices.mods.resolve(value);
|
||||
|
||||
if(!fragment_file.empty())
|
||||
BuildShader();
|
||||
});
|
||||
|
||||
datahandler("Fragment", [&](std::string& value) {
|
||||
fragment_file = devices.mods.resolve(value);
|
||||
|
||||
if(!vertex_file.empty())
|
||||
BuildShader();
|
||||
});
|
||||
|
||||
datahandler("Settings Reload", [&](std::string& value) {
|
||||
if(shader && toBool(value))
|
||||
settingsShaders.push_back(shader);
|
||||
});
|
||||
|
||||
datahandler("Variable", [&](std::string& value) {
|
||||
if(!shader)
|
||||
return;
|
||||
|
||||
//Split into left and right parts
|
||||
std::vector<std::string> parts;
|
||||
split(value, parts, '=');
|
||||
|
||||
if(parts.size() != 2)
|
||||
return;
|
||||
|
||||
//Find variable type and name
|
||||
std::vector<std::string> decl;
|
||||
split(parts[0], decl, ' ');
|
||||
|
||||
if(decl.size() != 2)
|
||||
return;
|
||||
|
||||
//Find arguments to variable
|
||||
std::vector<std::string> args;
|
||||
split(parts[1], args, ' ');
|
||||
|
||||
if(args.size() == 0)
|
||||
return;
|
||||
|
||||
//Find the correct variable type
|
||||
render::Shader::VarType type = render::Shader::VT_invalid;
|
||||
|
||||
std::string typeName, arraySizeText;
|
||||
unsigned arraySize;
|
||||
if(split(decl[0], typeName, '[', arraySizeText, ']')) {
|
||||
arraySize = atoi(arraySizeText.c_str());
|
||||
if(arraySize > 100 || arraySize == 0)
|
||||
return;
|
||||
}
|
||||
else {
|
||||
typeName = decl[0];
|
||||
arraySize = 1;
|
||||
}
|
||||
|
||||
if(typeName == "tex" || typeName == "int")
|
||||
type = render::Shader::VT_int;
|
||||
else if(typeName == "ivec2")
|
||||
type = render::Shader::VT_int2;
|
||||
else if(typeName == "ivec3")
|
||||
type = render::Shader::VT_int3;
|
||||
else if(typeName == "ivec4")
|
||||
type = render::Shader::VT_int4;
|
||||
else if(typeName == "float")
|
||||
type = render::Shader::VT_float;
|
||||
else if(typeName == "vec2")
|
||||
type = render::Shader::VT_float2;
|
||||
else if(typeName == "vec3")
|
||||
type = render::Shader::VT_float3;
|
||||
else if(typeName == "vec4")
|
||||
type = render::Shader::VT_float4;
|
||||
else if(typeName == "mat3")
|
||||
type = render::Shader::VT_mat3;
|
||||
|
||||
if(type == render::Shader::VT_invalid)
|
||||
return;
|
||||
|
||||
render::Shader::Variable var(type, arraySize);
|
||||
|
||||
if(args[0].find_first_not_of("0123456789.-+eE") != args[0].npos) {
|
||||
auto& call = args[0];
|
||||
if(call == "global") {
|
||||
if(args.size() >= 2) {
|
||||
ShaderGlobal* glob;
|
||||
auto it = shaderGlobals.find(args[1]);
|
||||
|
||||
if(it == shaderGlobals.end()) {
|
||||
glob = new ShaderGlobal();
|
||||
glob->name = args[1];
|
||||
glob->type = type;
|
||||
glob->arraySize = arraySize;
|
||||
shaderGlobals[glob->name] = glob;
|
||||
|
||||
switch(type) {
|
||||
case render::Shader::VT_int:
|
||||
glob->size = sizeof(int);
|
||||
break;
|
||||
case render::Shader::VT_int2:
|
||||
glob->size = sizeof(int) * 2;
|
||||
break;
|
||||
case render::Shader::VT_int3:
|
||||
glob->size = sizeof(int) * 3;
|
||||
break;
|
||||
case render::Shader::VT_int4:
|
||||
glob->size = sizeof(int) * 4;
|
||||
break;
|
||||
case render::Shader::VT_float:
|
||||
glob->size = sizeof(float);
|
||||
break;
|
||||
case render::Shader::VT_float2:
|
||||
glob->size = sizeof(float) * 2;
|
||||
break;
|
||||
case render::Shader::VT_float3:
|
||||
glob->size = sizeof(float) * 3;
|
||||
break;
|
||||
case render::Shader::VT_float4:
|
||||
glob->size = sizeof(float) * 4;
|
||||
break;
|
||||
case render::Shader::VT_mat3:
|
||||
glob->size = sizeof(float) * 9;
|
||||
break;
|
||||
}
|
||||
|
||||
glob->size *= arraySize;
|
||||
glob->ptr = malloc(glob->size);
|
||||
memset(glob->ptr, 0, glob->size);
|
||||
}
|
||||
else {
|
||||
glob = it->second;
|
||||
}
|
||||
|
||||
var._floatcall = shader_global;
|
||||
var._args = (void*)glob;
|
||||
var.constant = false;
|
||||
}
|
||||
}
|
||||
else switch(var.type) {
|
||||
case render::Shader::VT_int:
|
||||
if(call == "emp_flag") {
|
||||
var._intcall = shader_emp_flag;
|
||||
var.constant = false;
|
||||
}
|
||||
else if(call == "obj_id") {
|
||||
var._intcall = shader_obj_id;
|
||||
var.constant = false;
|
||||
}
|
||||
break;
|
||||
case render::Shader::VT_float:
|
||||
if(call == "time") {
|
||||
var._floatcall = shader_frameTime;
|
||||
}
|
||||
else if(call == "game_time") {
|
||||
var._floatcall = shader_gameTime;
|
||||
}
|
||||
else if(call == "unique") {
|
||||
var._floatcall = shader_unique;
|
||||
var.constant = false;
|
||||
}
|
||||
else if(call == "random") {
|
||||
var._floatcall = shader_random;
|
||||
}
|
||||
else if(call == "time_cycle") {
|
||||
var._floatcall = shader_frameTime_cycle;
|
||||
float* pArgs = new float[var.count];
|
||||
var._args = pArgs;
|
||||
|
||||
for(auto i = 0; i < var.count; ++i)
|
||||
pArgs[i] = 1000;
|
||||
loadToFloats(pArgs,args.begin()+1,
|
||||
(int)args.size() <= var.count+1 ? args.end() : args.begin()+(1+var.count));
|
||||
}
|
||||
else if(call == "game_time_cycle") {
|
||||
var._floatcall = shader_gameTime_cycle;
|
||||
float* pArgs = new float[var.count];
|
||||
var._args = pArgs;
|
||||
|
||||
for(auto i = 0; i < var.count; ++i)
|
||||
pArgs[i] = 1000;
|
||||
loadToFloats(pArgs,args.begin()+1,
|
||||
(int)args.size() <= var.count+1 ? args.end() : args.begin()+(1+var.count));
|
||||
}
|
||||
else if(call == "time_cycle_abs") {
|
||||
var._floatcall = shader_frameTime_cycle_abs;
|
||||
float* pArgs = new float[var.count];
|
||||
var._args = pArgs;
|
||||
|
||||
for(auto i = 0; i < var.count; ++i)
|
||||
pArgs[i] = 1000;
|
||||
loadToFloats(pArgs,args.begin()+1,
|
||||
(int)args.size() <= var.count+1 ? args.end() : args.begin()+(1+var.count));
|
||||
}
|
||||
else if(call == "game_time_cycle_abs") {
|
||||
var._floatcall = shader_gameTime_cycle_abs;
|
||||
float* pArgs = new float[var.count];
|
||||
var._args = pArgs;
|
||||
|
||||
for(auto i = 0; i < var.count; ++i)
|
||||
pArgs[i] = 1000;
|
||||
loadToFloats(pArgs,args.begin()+1,
|
||||
(int)args.size() <= var.count+1 ? args.end() : args.begin()+(1+var.count));
|
||||
}
|
||||
else if(call == "node_distance") {
|
||||
var._floatcall = shader_node_distance;
|
||||
var.constant = false;
|
||||
}
|
||||
else if(call == "node_selected") {
|
||||
var._floatcall = shader_node_selected;
|
||||
var.constant = false;
|
||||
}
|
||||
else if(call == "node_scale") {
|
||||
var._floatcall = shader_node_scale;
|
||||
var.constant = false;
|
||||
}
|
||||
else if(call == "obj_velocity") {
|
||||
var._floatcall = shader_obj_velocity;
|
||||
var.constant = false;
|
||||
}
|
||||
else if(call == "obj_acceleration") {
|
||||
var._floatcall = shader_obj_acceleration;
|
||||
var.constant = false;
|
||||
}
|
||||
else if(call == "plane_minrad") {
|
||||
var._floatcall = shader_plane_minrad;
|
||||
var.constant = false;
|
||||
}
|
||||
else if(call == "plane_maxrad") {
|
||||
var._floatcall = shader_plane_maxrad;
|
||||
var.constant = false;
|
||||
}
|
||||
else if(call == "state_vars") {
|
||||
var._floatcall = shader_statevars;
|
||||
var.constant = false;
|
||||
|
||||
int* pArgs = new int[var.count];
|
||||
memset(pArgs,0,sizeof(int) * var.count);
|
||||
var._args = pArgs;
|
||||
|
||||
unsigned index = 0;
|
||||
auto i = args.begin() + 1;
|
||||
auto end = args.end();
|
||||
for(; i != end && index < var.count; ++i, ++index)
|
||||
pArgs[index] = registerVar(*i);
|
||||
}
|
||||
else if(call == "pixel_ratio") {
|
||||
var._floatcall = shader_pixelRatio;
|
||||
}
|
||||
else if(call == "skin_grd_count") {
|
||||
var._floatcall = shader_skin_gradientCount;
|
||||
var.constant = false;
|
||||
}
|
||||
else if(call == "skin_grd_mode") {
|
||||
var._floatcall = shader_skin_gradientMode;
|
||||
var.constant = false;
|
||||
}
|
||||
else if(call == "light_radius") {
|
||||
var._floatcall = shader_light_radius;
|
||||
var.constant = true;
|
||||
|
||||
int* pArgs = new int[var.count];
|
||||
memset(pArgs,0,sizeof(int) * var.count);
|
||||
var._args = pArgs;
|
||||
|
||||
loadToInts(pArgs, args.begin()+1,
|
||||
(int)args.size() <= var.count+1 ? args.end() : args.begin()+(1+var.count));
|
||||
}
|
||||
else if(call == "light_active") {
|
||||
var._floatcall = shader_light_active;
|
||||
var.constant = true;
|
||||
|
||||
int* pArgs = new int[var.count];
|
||||
memset(pArgs,0,sizeof(int) * var.count);
|
||||
var._args = pArgs;
|
||||
|
||||
loadToInts(pArgs, args.begin()+1,
|
||||
(int)args.size() <= var.count+1 ? args.end() : args.begin()+(1+var.count));
|
||||
}
|
||||
break;
|
||||
case render::Shader::VT_float2:
|
||||
if(call == "skin_src_pos") {
|
||||
var._floatcall = shader_skin_src_pos;
|
||||
var.constant = false;
|
||||
}
|
||||
else if(call == "skin_src_size") {
|
||||
var._floatcall = shader_skin_src_size;
|
||||
var.constant = false;
|
||||
}
|
||||
else if(call == "skin_dst_size") {
|
||||
var._floatcall = shader_skin_dst_size;
|
||||
var.constant = false;
|
||||
}
|
||||
else if(call == "skin_dim_modes") {
|
||||
var._floatcall = shader_skin_mode;
|
||||
var.constant = false;
|
||||
}
|
||||
else if(call == "tex_size") {
|
||||
var._floatcall = shader_tex_size;
|
||||
int* pArgs = new int[var.count];
|
||||
memset(pArgs,0,sizeof(int) * var.count);
|
||||
var._args = pArgs;
|
||||
|
||||
loadToInts(pArgs, args.begin()+1,
|
||||
(int)args.size() <= var.count+1 ? args.end() : args.begin()+(1+var.count));
|
||||
}
|
||||
else if(call == "light_screen_position") {
|
||||
var._floatcall = shader_light_screen;
|
||||
var.constant = true;
|
||||
|
||||
int* pArgs = new int[var.count];
|
||||
memset(pArgs,0,sizeof(int) * var.count);
|
||||
var._args = pArgs;
|
||||
|
||||
loadToInts(pArgs, args.begin()+1,
|
||||
(int)args.size() <= var.count+1 ? args.end() : args.begin()+(1+var.count));
|
||||
}
|
||||
break;
|
||||
case render::Shader::VT_float3:
|
||||
if(call == "light_position") {
|
||||
var._floatcall = shader_light_position;
|
||||
var.constant = true;
|
||||
|
||||
int* pArgs = new int[var.count];
|
||||
memset(pArgs,0,sizeof(int) * var.count);
|
||||
var._args = pArgs;
|
||||
|
||||
loadToInts(pArgs, args.begin()+1,
|
||||
(int)args.size() <= var.count+1 ? args.end() : args.begin()+(1+var.count));
|
||||
}
|
||||
else if(call == "obj_position") {
|
||||
var._floatcall = shader_obj_position;
|
||||
var.constant = false;
|
||||
}
|
||||
else if(call == "node_position") {
|
||||
var._floatcall = shader_node_position;
|
||||
var.constant = false;
|
||||
}
|
||||
break;
|
||||
case render::Shader::VT_float4:
|
||||
if(call == "node_color") {
|
||||
var._floatcall = shader_node_color;
|
||||
var.constant = false;
|
||||
}
|
||||
else if(call == "skin_grd_colors") {
|
||||
var._floatcall = shader_skin_gradientCols;
|
||||
var.constant = false;
|
||||
}
|
||||
else if(call == "skin_grd_rects") {
|
||||
var._floatcall = shader_skin_gradientRects;
|
||||
var.constant = false;
|
||||
}
|
||||
else if(call == "skin_margin_src") {
|
||||
var._floatcall = shader_skin_margin_src;
|
||||
var.constant = false;
|
||||
}
|
||||
else if(call == "skin_margin_dest") {
|
||||
var._floatcall = shader_skin_margin_dest;
|
||||
var.constant = false;
|
||||
}
|
||||
else if(call == "sprite_pos") {
|
||||
var._floatcall = shader_sprite_pos;
|
||||
var.constant = false;
|
||||
}
|
||||
else if(call == "obj_quadrant_damage") {
|
||||
var._floatcall = shader_quadrant_damage;
|
||||
var.constant = false;
|
||||
}
|
||||
else if(call == "obj_rotation") {
|
||||
var._floatcall = shader_obj_rotation;
|
||||
var.constant = false;
|
||||
}
|
||||
else if(call == "node_rotation") {
|
||||
var._floatcall = shader_node_rotation;
|
||||
var.constant = false;
|
||||
}
|
||||
break;
|
||||
case render::Shader::VT_mat3:
|
||||
if(call == "inverse_view") {
|
||||
var._floatcall = shader_invView;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(var._intcall == 0) {
|
||||
auto from = args.begin(), to = args.size() <= var.count ? args.end() : args.begin()+var.count;
|
||||
switch(var.type) {
|
||||
case render::Shader::VT_int:
|
||||
loadToInts(var._ints, from, to); break;
|
||||
case render::Shader::VT_int2:
|
||||
loadToInt2s(var._ints, from, to); break;
|
||||
case render::Shader::VT_int3:
|
||||
loadToInt3s(var._ints, from, to); break;
|
||||
case render::Shader::VT_int4:
|
||||
loadToInt4s(var._ints, from, to); break;
|
||||
|
||||
case render::Shader::VT_float:
|
||||
loadToFloats(var._floats, from, to); break;
|
||||
case render::Shader::VT_float2:
|
||||
loadToFloat2s(var._floats, from, to); break;
|
||||
case render::Shader::VT_float3:
|
||||
loadToFloat3s(var._floats, from, to); break;
|
||||
case render::Shader::VT_float4:
|
||||
loadToFloat4s(var._floats, from, to); break;
|
||||
}
|
||||
}
|
||||
|
||||
shader->addVariable(decl[1], var);
|
||||
});
|
||||
|
||||
datahandler.read(filename);
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,339 @@
|
||||
#include "render/render_state.h"
|
||||
#include "main/references.h"
|
||||
#include "main/initialization.h"
|
||||
#include "resource/library.h"
|
||||
#include "gui/skin.h"
|
||||
#include "str_util.h"
|
||||
#include "num_util.h"
|
||||
#include "compat/misc.h"
|
||||
#include "main/logging.h"
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <functional>
|
||||
|
||||
namespace resource {
|
||||
|
||||
umap<std::string, DimensionMode> INIT_VAR(dimension_modes) {
|
||||
dimension_modes["Uniform"] = DM_Uniform;
|
||||
dimension_modes["Scaled"] = DM_Scaled;
|
||||
dimension_modes["Tiled"] = DM_Tiled;
|
||||
} INIT_VAR_END;
|
||||
|
||||
umap<std::string, GradientMode> INIT_VAR(gradient_modes) {
|
||||
gradient_modes["Normal"] = GM_Normal;
|
||||
gradient_modes["Overlay"] = GM_Overlay;
|
||||
} INIT_VAR_END;
|
||||
|
||||
umap<std::string, AspectMarginMode> INIT_VAR(aspect_modes) {
|
||||
aspect_modes["Horizontal"] = AMM_Horizontal;
|
||||
aspect_modes["Vertical"] = AMM_Vertical;
|
||||
aspect_modes["None"] = AMM_None;
|
||||
} INIT_VAR_END;
|
||||
|
||||
|
||||
void parseElementIdentifier(const std::string& str, int& style, unsigned& flags, bool addIfMissing) {
|
||||
std::vector<std::string> args;
|
||||
split(str, args, ',');
|
||||
|
||||
flags = 0;
|
||||
style = gui::skin::getStyleIndex(trim(args[0]), addIfMissing);
|
||||
|
||||
for(unsigned i = 1; i < args.size(); ++i)
|
||||
flags |= gui::skin::getElementFlag(trim(args[i]), addIfMissing);
|
||||
}
|
||||
|
||||
unsigned parseElementFlags(const std::string& str, bool addIfMissing) {
|
||||
std::vector<std::string> args;
|
||||
split(str, args, ',');
|
||||
|
||||
unsigned flags = 0;
|
||||
for(unsigned i = 0; i < args.size(); ++i)
|
||||
flags |= gui::skin::getElementFlag(trim(args[i]), addIfMissing);
|
||||
return flags;
|
||||
}
|
||||
|
||||
#define HANDLE_RELCOORD(handler, key, obj, member, pos) handler(key, [&](std::string& value) {\
|
||||
if(!obj || obj->member.empty())\
|
||||
return;\
|
||||
\
|
||||
auto& pos = obj->member.back().area.pos;\
|
||||
\
|
||||
if(value[value.size() - 1] == '%') {\
|
||||
value = value.substr(0, value.size() - 1);\
|
||||
double val = toNumber<double>(value);\
|
||||
\
|
||||
if(val < 0)\
|
||||
pos.set(RPT_Right, 0, -val / 100.0);\
|
||||
else\
|
||||
pos.set(RPT_Left, 0, val / 100.0);\
|
||||
}\
|
||||
else {\
|
||||
int val = toNumber<int>(value);\
|
||||
\
|
||||
if(val < 0)\
|
||||
pos.set(RPT_Right, -val, 0.0);\
|
||||
else\
|
||||
pos.set(RPT_Left, val, 0.0);\
|
||||
}\
|
||||
});
|
||||
|
||||
#define HANDLE_GCOLOR(handler, key, obj, member, pos) handler(key, [&](std::string& value) {\
|
||||
if(!obj || obj->member.empty())\
|
||||
return;\
|
||||
obj->member.back().pos = toColor(value);\
|
||||
});
|
||||
|
||||
void Library::loadSkins(const std::string& filename) {
|
||||
DataHandler datahandler;
|
||||
gui::skin::Skin* skin = 0;
|
||||
|
||||
//Handling for the skin styles file
|
||||
datahandler("Skin", [&](std::string& value) {
|
||||
if(isIdentifier(value)) {
|
||||
skin = new gui::skin::Skin();
|
||||
skins[value] = skin;
|
||||
}
|
||||
else {
|
||||
skin = 0;
|
||||
error("Skin '%s' is not a valid identifier", value.c_str());
|
||||
}
|
||||
});
|
||||
|
||||
datahandler("Material", [&](std::string& value) {
|
||||
if(!skin)
|
||||
return;
|
||||
skin->materialName = value;
|
||||
});
|
||||
|
||||
datahandler("File", [&](std::string& value) {
|
||||
if(!skin)
|
||||
return;
|
||||
std::string filename = devices.mods.resolve(value);
|
||||
loadSkin(filename, skin);
|
||||
skin_files[filename] = skin;
|
||||
|
||||
if(watch_resources)
|
||||
watchSkin(filename);
|
||||
});
|
||||
|
||||
datahandler.read(filename);
|
||||
}
|
||||
|
||||
void Library::loadSkin(const std::string& filename, gui::skin::Skin* skin) {
|
||||
DataHandler skinhandler;
|
||||
gui::skin::Element* ele = 0;
|
||||
gui::skin::Style* style = 0;
|
||||
|
||||
//Handling for global skin stuff
|
||||
skinhandler("Color", [&](std::string& value) {
|
||||
std::vector<std::string> args;
|
||||
split(value, args, '=');
|
||||
|
||||
if(args.size() != 2)
|
||||
return;
|
||||
|
||||
unsigned index = gui::skin::getColorIndex(trim(args[0]), true);
|
||||
skin->setColor(index, toColor(trim(args[1])));
|
||||
});
|
||||
|
||||
skinhandler("Font", [&](std::string& value) {
|
||||
std::vector<std::string> args;
|
||||
split(value, args, '=');
|
||||
|
||||
if(args.size() != 2)
|
||||
return;
|
||||
|
||||
unsigned index = gui::skin::getFontIndex(trim(args[0]), true);
|
||||
skin->setFont(index, &getFont(trim(args[1])));
|
||||
});
|
||||
|
||||
//Handling for styles and elements
|
||||
skinhandler("Style", [&](std::string& value) {
|
||||
if(!isIdentifier(value)) {
|
||||
style = 0;
|
||||
error("Style '%s' is not a valid identifier", value.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned index = gui::skin::getStyleIndex(value, true);
|
||||
if(!skin->hasStyle(index)) {
|
||||
style = new gui::skin::Style();
|
||||
skin->setStyle(index, style);
|
||||
}
|
||||
else {
|
||||
style = (gui::skin::Style*)&skin->getStyle(index);
|
||||
}
|
||||
ele = 0;
|
||||
});
|
||||
|
||||
skinhandler("Element", [&](std::string& value) {
|
||||
unsigned flags = parseElementFlags(value, true);
|
||||
|
||||
if(style) {
|
||||
ele = style->getExactElement(flags);
|
||||
if(!ele) {
|
||||
ele = new gui::skin::Element();
|
||||
ele->flags = flags;
|
||||
ele->material = skin->material;
|
||||
style->addElement(ele);
|
||||
}
|
||||
else {
|
||||
ele->clear();
|
||||
}
|
||||
}
|
||||
else {
|
||||
ele = new gui::skin::Element();
|
||||
ele->flags = flags;
|
||||
}
|
||||
});
|
||||
|
||||
skinhandler("Shape", [&](std::string& value) {
|
||||
if(!style)
|
||||
return;
|
||||
|
||||
if(value == "Regular")
|
||||
style->irregular = false;
|
||||
else
|
||||
style->irregular = true;
|
||||
});
|
||||
|
||||
skinhandler("Inherit", [&](std::string& value) {
|
||||
if(!style)
|
||||
return;
|
||||
|
||||
if(ele) {
|
||||
unsigned oldFlags = ele->flags;
|
||||
|
||||
int style;
|
||||
unsigned flags;
|
||||
parseElementIdentifier(value, style, flags, false);
|
||||
if(style == -1)
|
||||
return;
|
||||
|
||||
*ele = skin->getElement(style, flags);
|
||||
ele->flags = oldFlags;
|
||||
}
|
||||
else {
|
||||
int styleID;
|
||||
unsigned flags;
|
||||
parseElementIdentifier(value, styleID, flags, false);
|
||||
if(styleID == -1)
|
||||
return;
|
||||
|
||||
const gui::skin::Style& old = skin->getStyle(styleID);
|
||||
style->irregular = old.irregular;
|
||||
foreach(it, old.elements) {
|
||||
ele = new gui::skin::Element();
|
||||
*ele = **it;
|
||||
style->addElement(ele);
|
||||
}
|
||||
ele = 0;
|
||||
}
|
||||
});
|
||||
|
||||
skinhandler("Rect", [&](std::string& value) {
|
||||
if(!ele)
|
||||
return;
|
||||
|
||||
sscanf(value.c_str(), " [ %i , %i ] [ %i , %i ]", &ele->area.topLeft.x,
|
||||
&ele->area.topLeft.y, &ele->area.botRight.x, &ele->area.botRight.y);
|
||||
});
|
||||
|
||||
HANDLE_ENUM(skinhandler, "Horizontal", ele, horizMode, dimension_modes);
|
||||
HANDLE_ENUM(skinhandler, "Vertical", ele, vertMode, dimension_modes);
|
||||
HANDLE_ENUM(skinhandler, "AspectMargin", ele, aspectMargin, aspect_modes);
|
||||
HANDLE_ENUM(skinhandler, "GradientMode", ele, gradMode, gradient_modes);
|
||||
|
||||
HANDLE_BOOL(skinhandler, "Filled", ele, filled);
|
||||
skinhandler("Margin", [&](std::string& value) {
|
||||
std::vector<std::string> numbers;
|
||||
split(value, numbers, ',', true);
|
||||
|
||||
if(numbers.size() == 4) {
|
||||
ele->margin = recti(
|
||||
toNumber<int>(numbers[0]),
|
||||
toNumber<int>(numbers[1]),
|
||||
toNumber<int>(numbers[2]),
|
||||
toNumber<int>(numbers[3]));
|
||||
}
|
||||
else if(numbers.size() == 2) {
|
||||
int x = toNumber<int>(numbers[0]);
|
||||
int y = toNumber<int>(numbers[1]);
|
||||
ele->margin = recti(x, y, x, y);
|
||||
}
|
||||
else if(numbers.size() == 1) {
|
||||
int num = toNumber<int>(numbers[0]);
|
||||
ele->margin = recti(num, num, num, num);
|
||||
}
|
||||
else {
|
||||
error("Margin specifier '%s' invalid.", value.c_str());
|
||||
}
|
||||
});
|
||||
|
||||
//Handling for gradients
|
||||
skinhandler("Add Gradient", [&](std::string& value) {
|
||||
ele->gradients.push_back(gui::skin::Gradient());
|
||||
});
|
||||
|
||||
HANDLE_RELCOORD(skinhandler, "GX1", ele, gradients, left);
|
||||
HANDLE_RELCOORD(skinhandler, "GY1", ele, gradients, top);
|
||||
HANDLE_RELCOORD(skinhandler, "GX2", ele, gradients, right);
|
||||
HANDLE_RELCOORD(skinhandler, "GY2", ele, gradients, bottom);
|
||||
|
||||
HANDLE_GCOLOR(skinhandler, "TopLeft", ele, gradients, colors[0]);
|
||||
HANDLE_GCOLOR(skinhandler, "TopRight", ele, gradients, colors[1]);
|
||||
HANDLE_GCOLOR(skinhandler, "BotLeft", ele, gradients, colors[2]);
|
||||
HANDLE_GCOLOR(skinhandler, "BotRight", ele, gradients, colors[3]);
|
||||
|
||||
//Handling for layers
|
||||
skinhandler("Layer", [&](std::string& value) {
|
||||
if(!ele)
|
||||
return;
|
||||
|
||||
int style;
|
||||
unsigned flags;
|
||||
parseElementIdentifier(value, style, flags, false);
|
||||
if(style == -1)
|
||||
return;
|
||||
|
||||
ele->layers.push_back(gui::skin::Layer());
|
||||
ele->layers.back().ele = &skin->getElement(style, flags);
|
||||
});
|
||||
|
||||
HANDLE_RELCOORD(skinhandler, "OX1", ele, layers, left);
|
||||
HANDLE_RELCOORD(skinhandler, "OY1", ele, layers, top);
|
||||
HANDLE_RELCOORD(skinhandler, "OX2", ele, layers, right);
|
||||
HANDLE_RELCOORD(skinhandler, "OY2", ele, layers, bottom);
|
||||
|
||||
skinhandler("Color Override", [&](std::string& value) {
|
||||
if(!ele)
|
||||
return;
|
||||
if(ele->layers.size() == 0)
|
||||
return;
|
||||
|
||||
ele->layers.back().hasOverride = true;
|
||||
ele->layers.back().override = toColor(value);
|
||||
});
|
||||
|
||||
//Read styles file
|
||||
skinhandler.read(filename);
|
||||
}
|
||||
|
||||
void Library::bindSkinMaterials() {
|
||||
foreach(it, skins) {
|
||||
const render::RenderState& mat = getMaterial(it->second->materialName);
|
||||
it->second->material = &mat;
|
||||
|
||||
foreach(style, it->second->styles) {
|
||||
if(*style) {
|
||||
foreach(ele, (*style)->elements) {
|
||||
(*ele)->material = &mat;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,217 @@
|
||||
#include "main/references.h"
|
||||
#include "main/logging.h"
|
||||
#include "main/initialization.h"
|
||||
#include "resource/library.h"
|
||||
#include "ISound.h"
|
||||
#include "ISoundSource.h"
|
||||
#include "ISoundDevice.h"
|
||||
#include "SLoadError.h"
|
||||
#include "files.h"
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <queue>
|
||||
|
||||
namespace resource {
|
||||
|
||||
struct QueuedSound {
|
||||
int priority;
|
||||
Sound* snd;
|
||||
std::string filename;
|
||||
float volume;
|
||||
|
||||
bool operator<(const QueuedSound& other) const {
|
||||
return priority < other.priority;
|
||||
}
|
||||
};
|
||||
std::priority_queue<QueuedSound> queuedSounds;
|
||||
|
||||
audio::ISound* Sound::play2D(bool loop, bool pause, bool priority) const {
|
||||
if(!loaded)
|
||||
return nullptr;
|
||||
|
||||
if(source) {
|
||||
auto* ptr = devices.sound->play2D(source, loop, true, priority);
|
||||
if(ptr) {
|
||||
ptr->grab();
|
||||
if(!pause)
|
||||
ptr->resume();
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
else if(!streamSource.empty()) {
|
||||
try {
|
||||
auto* source = devices.sound->loadStreamingSound(streamSource);
|
||||
if(source) {
|
||||
source->setDefaultVolume(volume);
|
||||
audio::ISound* sound = devices.sound->play2D(source, loop, true, priority);
|
||||
if(sound) {
|
||||
sound->grab();
|
||||
if(!pause)
|
||||
sound->resume();
|
||||
}
|
||||
source->drop();
|
||||
return sound;
|
||||
}
|
||||
else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
catch(audio::SLoadError) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
audio::ISound* Sound::play3D(const vec3d& pos, bool loop, bool pause, bool priority) const {
|
||||
if(!loaded)
|
||||
return nullptr;
|
||||
|
||||
if(source) {
|
||||
auto* ptr = devices.sound->play3D(source, snd_vec(pos), loop, true, priority);
|
||||
if(ptr) {
|
||||
ptr->grab();
|
||||
if(!pause)
|
||||
ptr->resume();
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
else if(!streamSource.empty()) {
|
||||
try {
|
||||
auto* source = devices.sound->loadStreamingSound(streamSource);
|
||||
if(source) {
|
||||
source->setDefaultVolume(volume);
|
||||
audio::ISound* sound = devices.sound->play3D(source, snd_vec(pos), loop, true, priority);
|
||||
if(sound) {
|
||||
sound->grab();
|
||||
if(!pause)
|
||||
sound->resume();
|
||||
}
|
||||
source->drop();
|
||||
return sound;
|
||||
}
|
||||
else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
catch(audio::SLoadError) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Library::hasQueuedSounds() {
|
||||
return !queuedSounds.empty();
|
||||
}
|
||||
|
||||
bool Library::processSounds(int maxPriority, int amount) {
|
||||
bool processedAny = false;
|
||||
int i = 0;
|
||||
while(!queuedSounds.empty()) {
|
||||
auto elem = queuedSounds.top();
|
||||
if(elem.priority < maxPriority)
|
||||
break;
|
||||
queuedSounds.pop();
|
||||
|
||||
processedAny = true;
|
||||
|
||||
audio::ISoundSource* source = 0;
|
||||
|
||||
try {
|
||||
source = devices.sound->loadSound(elem.filename.c_str());
|
||||
if(source) {
|
||||
source->setDefaultVolume(elem.volume);
|
||||
elem.snd->source = source;
|
||||
elem.snd->loaded = true;
|
||||
}
|
||||
}
|
||||
catch(const audio::SLoadError& err) {
|
||||
error("Could not load sound '%s': %s", elem.filename.c_str(), err.what());
|
||||
source = 0;
|
||||
}
|
||||
|
||||
++i;
|
||||
|
||||
if(i >= amount)
|
||||
break;
|
||||
}
|
||||
|
||||
return processedAny;
|
||||
}
|
||||
|
||||
void Library::loadSounds(const std::string& filename) {
|
||||
std::string sound_name, sound_file;
|
||||
float volume = 1.f;
|
||||
int priority = -61;
|
||||
bool streamed = false;
|
||||
|
||||
DataHandler datahandler;
|
||||
|
||||
auto makeSound = [&]() {
|
||||
if(sound_name.empty() || sound_file.empty())
|
||||
return;
|
||||
|
||||
Sound* snd = new Sound();
|
||||
if(load_resources && use_sound) {
|
||||
if(streamed) {
|
||||
snd->streamSource = devices.mods.resolve(sound_file);
|
||||
snd->volume = volume;
|
||||
if(fileExists(snd->streamSource))
|
||||
snd->loaded = true;
|
||||
else
|
||||
error("Could not locate streaming sound source: '%s'", sound_file.c_str());
|
||||
}
|
||||
else {
|
||||
resource::QueuedSound queued = {priority, snd, devices.mods.resolve(sound_file), volume};
|
||||
queuedSounds.push(queued);
|
||||
}
|
||||
}
|
||||
sounds[sound_name] = snd;
|
||||
|
||||
sound_name.clear();
|
||||
sound_file.clear();
|
||||
volume = 1.f;
|
||||
priority = -61;
|
||||
streamed = false;
|
||||
};
|
||||
|
||||
datahandler("Sound", [&](std::string& value) {
|
||||
makeSound();
|
||||
sound_name = value;
|
||||
});
|
||||
|
||||
datahandler("Stream", [&](std::string& value) {
|
||||
makeSound();
|
||||
sound_name = value;
|
||||
streamed = true;
|
||||
});
|
||||
|
||||
datahandler("File", [&](std::string& value) {
|
||||
sound_file = value;
|
||||
});
|
||||
|
||||
datahandler("Volume", [&](std::string& value) {
|
||||
volume = toNumber<float>(value);
|
||||
});
|
||||
|
||||
datahandler("LoadPriority", [&](std::string& value) {
|
||||
if(streamed)
|
||||
error("LoadPriority is not valid for streaming sounds");
|
||||
if(value == "Critical" || value == "Menu")
|
||||
priority = 10;
|
||||
else if(value == "Game")
|
||||
priority = -10;
|
||||
else
|
||||
priority = -111 + toNumber<int>(value);
|
||||
});
|
||||
|
||||
datahandler.read(filename);
|
||||
makeSound();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
#include "resource/locale.h"
|
||||
#include "threads.h"
|
||||
#include "str_util.h"
|
||||
#include "util/format.h"
|
||||
#include "main/logging.h"
|
||||
#include <stdarg.h>
|
||||
|
||||
namespace resource {
|
||||
|
||||
void Locale::clear() {
|
||||
foreach(it, localizations)
|
||||
delete it->second;
|
||||
localizations.clear();
|
||||
hashLocalizations.clear();
|
||||
}
|
||||
|
||||
void Locale::load(const std::string& filename) {
|
||||
DataReader datafile(filename);
|
||||
while(datafile++) {
|
||||
if(!datafile.value.empty() && datafile.value[0] == '\"')
|
||||
datafile.value = datafile.value.substr(1, datafile.value.size() - 2);
|
||||
|
||||
if(isIdentifier(datafile.key)) {
|
||||
std::string* str = new std::string(unescape(datafile.value));
|
||||
localizations[datafile.key] = str;
|
||||
|
||||
std::string withHash = "#";
|
||||
withHash += datafile.key;
|
||||
hashLocalizations[withHash] = str;
|
||||
}
|
||||
else {
|
||||
error("Locale key '%s' is not a valid identifier", datafile.key.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string Locale::localize(const std::string& text, bool requireHash, bool doUnescape, bool doFormat) {
|
||||
bool hasHash = !text.empty() && text[0] == '#';
|
||||
if(requireHash && !hasHash)
|
||||
return doUnescape ? unescape(text) : text;
|
||||
|
||||
auto pos = doFormat ? text.find(':') : std::string::npos;
|
||||
if(pos == std::string::npos) {
|
||||
if(hasHash) {
|
||||
auto it = hashLocalizations.find(text);
|
||||
if(it == hashLocalizations.end())
|
||||
return doUnescape ? unescape(text) : text;
|
||||
return *it->second;
|
||||
}
|
||||
else {
|
||||
auto it = localizations.find(text);
|
||||
if(it == localizations.end())
|
||||
return doUnescape ? unescape(text) : text;
|
||||
return *it->second;
|
||||
}
|
||||
}
|
||||
else {
|
||||
std::string result;
|
||||
std::vector<std::string> arguments;
|
||||
split(text, arguments, ':', false, true);
|
||||
|
||||
if(hasHash) {
|
||||
auto it = hashLocalizations.find(arguments[0]);
|
||||
if(it == hashLocalizations.end())
|
||||
return doUnescape ? unescape(arguments[0]) : arguments[0];
|
||||
result = *it->second;
|
||||
}
|
||||
else {
|
||||
auto it = localizations.find(arguments[0]);
|
||||
if(it == localizations.end())
|
||||
return doUnescape ? unescape(arguments[0]) : arguments[0];
|
||||
result = *it->second;
|
||||
}
|
||||
|
||||
FormatArg args[16];
|
||||
unsigned argCnt = arguments.size();
|
||||
for(unsigned i = 1; i < argCnt; ++i) {
|
||||
args[i-1].type = FormatArg::Arg_string;
|
||||
args[i-1].s = &arguments[i];
|
||||
}
|
||||
|
||||
std::string output;
|
||||
format(output, result.c_str(), argCnt-1, args);
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
Locale::~Locale() {
|
||||
clear();
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
#include "compat/misc.h"
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace resource {
|
||||
|
||||
class Locale {
|
||||
public:
|
||||
umap<std::string, std::string*> localizations;
|
||||
umap<std::string, std::string*> hashLocalizations;
|
||||
|
||||
void clear();
|
||||
void load(const std::string& filename);
|
||||
|
||||
std::string localize(const std::string& text, bool requireHash = false, bool doUnescape = true, bool doFormat = true);
|
||||
~Locale();
|
||||
};
|
||||
|
||||
};
|
||||
Reference in New Issue
Block a user