scummvm/backends/graphics/opengl/pipelines/libretro/parser.cpp
2023-01-08 01:15:51 +01:00

569 lines
15 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "graphics/opengl/system_headers.h"
#if !USE_FORCED_GLES
#include "backends/graphics/opengl/pipelines/libretro/parser.h"
#include "common/file.h"
#include "common/hash-str.h"
#include "common/stream.h"
#include "common/algorithm.h"
#include "common/tokenizer.h"
#include "common/ptr.h"
#include "common/util.h"
#include "common/textconsole.h"
namespace OpenGL {
namespace LibRetro {
class PresetParser {
public:
ShaderPreset *parseStream(Common::SeekableReadStream &stream);
const Common::String &getErrorDesc() const { return _errorDesc; }
private:
bool parsePreset(Common::SeekableReadStream &stream);
bool lookUpValue(const Common::String &key, Common::String *value);
bool lookUpValue(const Common::String &key, bool *value);
bool lookUpValue(const Common::String &key, uint *value);
bool lookUpValue(const Common::String &key, float *value);
bool lookUpValue(const Common::String &key, FilteringMode *value, const FilteringMode defaultValue);
bool lookUpValue(const Common::String &key, ScaleType *value, const ScaleType defaultValue);
bool lookUpValueScale(const Common::String &key, float *floatValue, uint *uintValue, const ScaleType scaleType);
bool lookUpValue(const Common::String &key, WrapMode *value, const WrapMode defaultValue);
template<typename T, typename DefaultT>
bool lookUpValue(const Common::String &key, T *value, const DefaultT &defaultValue) {
if (_entries.contains(key)) {
return lookUpValue(key, value);
} else {
*value = defaultValue;
return true;
}
}
bool parseTextures();
bool parseTexture(const Common::String &id);
bool parsePasses();
bool parsePass(const uint id, const bool isLast);
bool parsePassScaleType(const uint id, const bool isLast, ShaderPass *pass);
bool parsePassScale(const uint id, ShaderPass *pass);
bool computeDefaultScale(const Common::String &key, float *floatValue, uint *uintValue, const ScaleType scaleType);
bool parseParameters();
typedef Common::HashMap<Common::String, Common::String> StringMap;
StringMap _entries;
Common::String _errorDesc;
Common::ScopedPtr<ShaderPreset> _shader;
};
ShaderPreset *PresetParser::parseStream(Common::SeekableReadStream &stream) {
_errorDesc.clear();
_entries.clear();
if (!parsePreset(stream)) {
return nullptr;
}
_shader.reset(new ShaderPreset);
if (!parseTextures()) {
return nullptr;
}
if (!parsePasses()) {
return nullptr;
}
if (!parseParameters()) {
return nullptr;
}
return _shader.release();
}
bool PresetParser::parsePreset(Common::SeekableReadStream &stream) {
while (!stream.eos()) {
Common::String line = stream.readLine();
if (stream.err()) {
_errorDesc = "Read error";
return false;
}
size_t sharpPos = line.findFirstOf('#');
if (sharpPos != line.npos) {
// Remove the end of line
line.erase(sharpPos);
}
if (line.empty()) {
continue;
}
bool empty = true;
for (uint i = 0; i < line.size(); i++) {
if (!Common::isSpace(line[i])) {
empty = false;
break;
}
}
if (empty)
continue;
// Split line into key, value pair.
Common::String::const_iterator equalIter = Common::find(line.begin(), line.end(), '=');
if (equalIter == line.end()) {
_errorDesc = "Could not find '=' in line '" + line + '\'';
return false;
}
Common::String key(line.begin(), equalIter);
Common::String value(equalIter + 1);
key.trim();
value.trim();
// Check whether the value is put in quotation marks. This might be
// useful when a path contains a whitespace. But Libretro's is not
// mentioning any exact format, but one example for 'textures'
// indicates quotes are supported in this place.
if (!value.empty() && value[0] == '"') {
if (value.size() < 2 || value.lastChar() != '"') {
_errorDesc = "Unmatched '\"' for value in line '" + line + '\'';
}
value.deleteLastChar();
value.deleteChar(0);
}
_entries[key] = value;
}
return true;
}
bool PresetParser::lookUpValue(const Common::String &key, Common::String *value) {
StringMap::const_iterator iter = _entries.find(key);
if (iter != _entries.end()) {
*value = iter->_value;
return true;
} else {
_errorDesc = "Missing key '" + key + "'";
return false;
}
}
bool PresetParser::lookUpValue(const Common::String &key, bool *value) {
StringMap::const_iterator iter = _entries.find(key);
if (iter != _entries.end()) {
if (Common::parseBool(iter->_value, *value)) {
return true;
} else {
_errorDesc = "Invalid boolean value for key '" + key + "': '" + iter->_value + '\'';
return false;
}
} else {
_errorDesc = "Missing key '" + key + "'";
return false;
}
}
bool PresetParser::lookUpValue(const Common::String &key, uint *value) {
StringMap::const_iterator iter = _entries.find(key);
if (iter != _entries.end()) {
char *endptr;
const unsigned long uintVal = strtoul(iter->_value.c_str(), &endptr, 0);
// Original libretro is quite laxist with int values and only checks errno
// This means that as long as there is some number at start, parsing won't fail
if (endptr == iter->_value.c_str() || uintVal >= UINT_MAX) {
_errorDesc = "Invalid unsigned integer value for key '" + key + "': '" + iter->_value + '\'';
return false;
} else {
if (*endptr != '\0') {
warning("Possibly invalid unsigned integer value for key '%s': '%s'", key.c_str(), iter->_value.c_str());
}
*value = uintVal;
return true;
}
} else {
_errorDesc = "Missing key '" + key + "'";
return false;
}
}
bool PresetParser::lookUpValue(const Common::String &key, float *value) {
StringMap::const_iterator iter = _entries.find(key);
if (iter != _entries.end()) {
char *endptr;
const double doubleVal = strtod(iter->_value.c_str(), &endptr);
if (*endptr != '\0') {
_errorDesc = "Invalid float value for key '" + key + "': '" + iter->_value + '\'';
return false;
} else {
*value = doubleVal;
return true;
}
} else {
_errorDesc = "Missing key '" + key + "'";
return false;
}
}
bool PresetParser::lookUpValue(const Common::String &key, FilteringMode *value, const FilteringMode defaultValue) {
StringMap::const_iterator iter = _entries.find(key);
if (iter != _entries.end()) {
bool isLinear;
if (Common::parseBool(iter->_value, isLinear)) {
*value = isLinear ? kFilteringModeLinear : kFilteringModeNearest;
return true;
} else {
_errorDesc = "Invalid filtering mode for key '" + key + "': '" + iter->_value + '\'';
return false;
}
} else {
*value = defaultValue;
return true;
}
}
bool PresetParser::lookUpValue(const Common::String &key, ScaleType *value, const ScaleType defaultValue) {
StringMap::const_iterator iter = _entries.find(key);
if (iter != _entries.end()) {
if (iter->_value == "source") {
*value = kScaleTypeSource;
return true;
} else if (iter->_value == "viewport") {
*value = kScaleTypeViewport;
return true;
} else if (iter->_value == "absolute") {
*value = kScaleTypeAbsolute;
return true;
} else {
_errorDesc = "Invalid scale type for key '" + key + "': '" + iter->_value + '\'';
return false;
}
} else {
*value = defaultValue;
return true;
}
}
bool PresetParser::lookUpValueScale(const Common::String &key, float *floatValue, uint *uintValue, const ScaleType scaleType) {
switch (scaleType) {
case kScaleTypeSource:
case kScaleTypeViewport:
return lookUpValue(key, floatValue, 1.0f);
case kScaleTypeAbsolute:
return lookUpValue(key, uintValue);
case kScaleTypeFull:
return true;
default:
_errorDesc = "Internal Error: Invalid scale type";
return false;
}
}
bool PresetParser::lookUpValue(const Common::String &key, WrapMode *value, const WrapMode defaultValue) {
StringMap::const_iterator iter = _entries.find(key);
if (iter != _entries.end()) {
if (iter->_value == "clamp_to_border") {
*value = kWrapModeBorder;
return true;
} else if (iter->_value == "clamp_to_edge") {
*value = kWrapModeEdge;
return true;
} else if (iter->_value == "repeat") {
*value = kWrapModeRepeat;
return true;
} else if (iter->_value == "mirrored_repeat") {
*value = kWrapModeMirroredRepeat;
return true;
} else {
_errorDesc = "Invalid wrap mode for key '" + key + "': '" + iter->_value + '\'';
return false;
}
} else {
*value = defaultValue;
return true;
}
}
bool PresetParser::parseTextures() {
Common::String textures;
if (!lookUpValue("textures", &textures)) {
return true;
}
// Parse all texture information from preset.
Common::StringTokenizer tokenizer(textures, ";");
while (!tokenizer.empty()) {
if (!parseTexture(tokenizer.nextToken())) {
return false;
}
}
return true;
}
bool PresetParser::parseTexture(const Common::String &id) {
Common::String fileName;
if (!lookUpValue(id, &fileName)) {
_errorDesc = "No file name specified for texture '" + id + '\'';
return false;
}
FilteringMode filteringMode;
if (!lookUpValue(id + "_linear", &filteringMode, kFilteringModeLinear)) {
return false;
}
WrapMode wrapMode;
if (!lookUpValue(id + "_wrap_mode", &wrapMode, kWrapModeBorder)) {
return false;
}
_shader->textures.push_back(ShaderTexture(id, fileName, filteringMode, wrapMode));
return true;
}
bool PresetParser::parsePasses() {
uint numShaders;
if (!lookUpValue("shaders", &numShaders)) {
return false;
}
for (uint shaderPass = 0; shaderPass < numShaders; ++shaderPass) {
if (!parsePass(shaderPass, shaderPass == numShaders - 1)) {
return false;
}
}
return true;
}
#define passKey(x) Common::String::format(x "%u", id)
bool PresetParser::parsePass(const uint id, const bool isLast) {
ShaderPass pass;
if (!lookUpValue(passKey("shader"), &pass.fileName)) {
_errorDesc = Common::String::format("No file name specified for pass '%u'", id);
return false;
}
if (!lookUpValue(passKey("alias"), &pass.alias)) {
_errorDesc.clear();
pass.alias.clear();
}
if (!lookUpValue(passKey("filter_linear"), &pass.filteringMode, kFilteringModeUnspecified)) {
return false;
}
if (!lookUpValue(passKey("wrap_mode"), &pass.wrapMode, kWrapModeBorder)) {
return false;
}
if (!lookUpValue(passKey("mipmap_input"), &pass.mipmapInput, false)) {
return false;
}
if (!lookUpValue(passKey("float_framebuffer"), &pass.floatFBO, false)) {
return false;
}
if (!lookUpValue(passKey("srgb_framebuffer"), &pass.srgbFBO, false)) {
return false;
}
if (!lookUpValue(passKey("frame_count_mod"), &pass.frameCountMod, 0)) {
return false;
}
if (!parsePassScaleType(id, isLast, &pass)) {
return false;
}
if (!parsePassScale(id, &pass)) {
return false;
}
_shader->passes.push_back(pass);
return true;
}
bool PresetParser::parsePassScaleType(const uint id, const bool isLast, ShaderPass *pass) {
// Parse scale type for the pass.
//
// This is a little more complicated because it is possible to specify the
// scale type per axis. However, a generic scale type overrides the axis
// scale types.
//
// Additionally, the default value for the passes vary. The last pass
// defaults to use full size, all other default to source scaling.
const ScaleType defaultScaleType = isLast ? kScaleTypeFull : kScaleTypeSource;
if (!lookUpValue(passKey("scale_type_x"), &pass->scaleTypeX, defaultScaleType)) {
return false;
}
if (!lookUpValue(passKey("scale_type_y"), &pass->scaleTypeY, defaultScaleType)) {
return false;
}
ScaleType scale_type;
// Small trick here: lookUpValue never returns kScaleTypeFull
if (!lookUpValue(passKey("scale_type"), &scale_type, kScaleTypeFull)) {
return false;
} else if (scale_type != kScaleTypeFull) {
pass->scaleTypeY = pass->scaleTypeX = scale_type;
}
return true;
}
bool PresetParser::parsePassScale(const uint id, ShaderPass *pass) {
// Parse actual scale value for the pass.
//
// Like for the scale type, 'scale' overrides 'scale_x'/'scale_y'.
if (_entries.contains(passKey("scale"))) {
if (!lookUpValueScale(passKey("scale"), &pass->scaleXFloat, &pass->scaleXUint, pass->scaleTypeX)) {
return false;
}
if (!lookUpValueScale(passKey("scale"), &pass->scaleYFloat, &pass->scaleYUint, pass->scaleTypeY)) {
return false;
}
return true;
}
if (_entries.contains(passKey("scale_x"))) {
if (!lookUpValueScale(passKey("scale_x"), &pass->scaleXFloat, &pass->scaleXUint, pass->scaleTypeX)) {
return false;
}
} else {
if (!computeDefaultScale(passKey("scale_x"), &pass->scaleXFloat, &pass->scaleXUint, pass->scaleTypeX)) {
return false;
}
}
if (_entries.contains(passKey("scale_y"))) {
if (!lookUpValueScale(passKey("scale_y"), &pass->scaleYFloat, &pass->scaleYUint, pass->scaleTypeY)) {
return false;
}
} else {
if (!computeDefaultScale(passKey("scale_y"), &pass->scaleYFloat, &pass->scaleYUint, pass->scaleTypeY)) {
return false;
}
}
return true;
}
#undef passKey
bool PresetParser::computeDefaultScale(const Common::String &key, float *floatValue, uint *uintValue, const ScaleType scaleType) {
switch (scaleType) {
case kScaleTypeSource:
case kScaleTypeViewport:
*floatValue = 1.0f;
return true;
case kScaleTypeAbsolute:
_errorDesc = "No value specified for scale '" + key + '\'';
return false;
case kScaleTypeFull:
return true;
default:
_errorDesc = "Internal Error: Invalid scale type";
return false;
}
}
bool PresetParser::parseParameters() {
Common::String parameters;
if (!lookUpValue("parameters", &parameters)) {
return true;
}
// Parse all texture information from preset.
Common::StringTokenizer tokenizer(parameters, ";");
while (!tokenizer.empty()) {
Common::String key = tokenizer.nextToken();
if (_entries.contains(key)) {
float value;
if (!lookUpValue(key, &value)) {
return false;
}
_shader->parameters[key] = value;
}
}
return true;
}
ShaderPreset *parsePreset(const Common::String &shaderPreset, Common::SearchSet &archSet) {
Common::SeekableReadStream *stream;
// First try SearchMan, then fallback to filesystem
if (archSet.hasFile(shaderPreset)) {
stream = archSet.createReadStreamForMember(shaderPreset);
} else {
Common::FSNode fsnode(shaderPreset);
if (!fsnode.exists() || !fsnode.isReadable() || fsnode.isDirectory()
|| !(stream = fsnode.createReadStream())) {
warning("LibRetro Preset Parsing: Invalid file path '%s'", shaderPreset.c_str());
return nullptr;
}
}
Common::String basePath(Common::firstPathComponents(shaderPreset, '/'));
PresetParser parser;
ShaderPreset *shader = parser.parseStream(*stream);
delete stream;
if (!shader) {
warning("LibRetro Preset Parsing: Error while parsing file '%s': %s", shaderPreset.c_str(), parser.getErrorDesc().c_str());
return nullptr;
}
shader->basePath = basePath;
return shader;
}
} // End of namespace LibRetro
} // End of namespace OpenGL
#endif // !USE_FORCED_GLES