blob: 4d2663e4b3b78c9a4b36927f4d903bb4adec9bce [file] [log] [blame] [edit]
/**************************************************************************
*
* Copyright 2015 Brian Paul
* All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
**************************************************************************/
/*
* Configuration file support.
*
* Used to override glGetString/Integer/etc queries.
*/
/*
* The format of the file is extremely simple. An example is given in
* docs/USAGE.markdown .
*
* Future improvements:
* - An option to simply turn off specific extensions
*
*/
#include <assert.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
#include "os.hpp"
#include "os_string.hpp"
#include "config.hpp"
namespace gltrace {
/**
* Parse the conf->extensions string to generate an array of individual
* extension strings which can be queried with glGetStringi().
*/
static void
create_extensions_list(configuration *conf)
{
// count extensions
const char *ext = conf->extensions.c_str();
conf->numExtensions = 0;
while (1) {
ext = strstr(ext, "GL_");
if (!ext)
break;
conf->numExtensions++;
// advance extensions pointer
while (*ext && *ext != ' ')
ext++;
}
conf->extensionsList =
(char **) malloc(conf->numExtensions * sizeof(char *));
// extract individual extension names
int count = 0;
const char *extStart = conf->extensions.c_str(), *extEnd;
while (1) {
extStart = strstr(extStart, "GL_");
if (!extStart)
break;
// find end of the name
extEnd = extStart;
while (*extEnd && *extEnd != ' ')
extEnd++;
// copy extension string name
int extLen = extEnd - extStart;
conf->extensionsList[count] = (char *) malloc(extLen + 1);
memcpy(conf->extensionsList[count], extStart, extLen);
conf->extensionsList[count][extLen] = 0;
count++;
// advance to next
extStart = extEnd;
}
assert(count == conf->numExtensions);
}
static bool
matchKeyword(const char *string, const char *keyword)
{
if (strncmp(string, keyword, strlen(keyword)) == 0)
return true;
else
return false;
}
static char buffer[10*1000];
static char
getChar(FILE *f, const char **buf)
{
if (feof(f))
return 0;
if (buf[0][0]) {
char c = buf[0][0];
(*buf)++;
return c;
}
while (1) {
// read another line
*buf = fgets(buffer, sizeof(buffer), f);
if (!buf || !buf[0]) {
return 0;
}
else if (buf[0][0]) {
char c = buf[0][0];
(*buf)++;
return c;
}
else if (feof(f)) {
return 0;
}
}
}
// Scan 'buf' for a string value of the form: "string" (double-quoted string)
// Return the string in newly malloc'd memory
static std::string
stringValue(FILE *f, const char *buf)
{
std::string result;
char c;
// look for =
do {
c = getChar(f, &buf);
} while (c && c != '=');
if (!c)
return result;
// look for opening "
do {
c = getChar(f, &buf);
} while (c && c != '\"');
if (!c)
return result;
// scan characters in the string
while (1) {
c = getChar(f, &buf);
if (!c) {
std::cerr << "Error: didn't find closing \" in config file!\n";
return result;
}
if (c == '\n') {
// convert newlines inside a string into spaces
c = ' ';
}
if (c == '\"') {
// end of string
return result;
}
else {
result.push_back(c);
}
}
}
// Scan integer value of the form: 12345
static int
intValue(FILE *f, const char *buf)
{
char valBuf[100];
int valLen = 0;
char c;
// look for =
do {
c = getChar(f, &buf);
} while (c && c != '=');
if (!c)
return 0;
// scan digits
while (1) {
c = getChar(f, &buf);
if (isdigit(c)) {
valBuf[valLen++] = c;
}
else if (c != ' ') {
break;
}
}
valBuf[valLen] = 0;
return atoi(valBuf);
}
static void
parse_file(FILE *f, configuration *conf)
{
do {
// read a line
char *b = fgets(buffer, sizeof(buffer), f);
if (!b) {
break;
}
else if (b[0] == '#') {
// comment - skip line
}
else if (b[0] == '\n') {
// empty line
}
else if (matchKeyword(b, "GL_VENDOR")) {
conf->vendor = stringValue(f, b + 10);
}
else if (matchKeyword(b, "GL_VERSION")) {
conf->version = stringValue(f, b + 11);
}
else if (matchKeyword(b, "GL_EXTENSIONS")) {
conf->extensions = stringValue(f, b + 14);
}
else if (matchKeyword(b, "GL_RENDERER")) {
conf->renderer = stringValue(f, b + 12);
}
else if (matchKeyword(b, "GL_SHADING_LANGUAGE_VERSION")) {
conf->glslVersion = stringValue(f, b + 28);
}
else if (matchKeyword(b, "GL_MAX_TEXTURE_SIZE")) {
conf->maxTextureSize = intValue(f, b + 20);
}
else if (matchKeyword(b, "GL_MAJOR_VERSION")) {
conf->versionMajor = intValue(f, b + 17);
}
else if (matchKeyword(b, "GL_MINOR_VERSION")) {
conf->versionMinor = intValue(f, b + 17);
}
else if (matchKeyword(b, "GL_CONTEXT_PROFILE_MASK")) {
std::string maskStr = stringValue(f, b + 24);
conf->profileMask = 0x0;
if (maskStr.find("GL_CONTEXT_CORE_PROFILE_BIT") != std::string::npos)
conf->profileMask |= GL_CONTEXT_CORE_PROFILE_BIT;
if (maskStr.find("GL_CONTEXT_COMPATIBILITY_PROFILE_BIT") != std::string::npos)
conf->profileMask |= GL_CONTEXT_COMPATIBILITY_PROFILE_BIT;
}
else {
std::cerr << "Unexpected config variable: " << b << ".\n";
break;
}
} while (!feof(f));
if (!conf->version.empty()) {
// String version was specified, compute integer major/minor versions
conf->versionMajor = conf->version[0] - '0';
conf->versionMinor = conf->version[2] - '0';
assert(conf->versionMajor >= 1 && conf->versionMajor <= 4);
assert(conf->versionMinor >= 0 && conf->versionMinor <= 9);
}
else if (conf->versionMajor) {
// Numeric version was specified, update the string
if (!conf->version.empty()) {
// if version string was specified too, override it
conf->version[0] = '0' + conf->versionMajor;
conf->version[2] = '0' + conf->versionMinor;
}
else {
// allocate new version string
conf->version.clear();
conf->version.push_back('0' + conf->versionMajor);
conf->version.push_back('.');
conf->version.push_back('0' + conf->versionMinor);
}
}
if (!conf->extensions.empty()) {
create_extensions_list(conf);
}
}
// Read the given configuration file and return a new configuration object.
// Return NULL if anything goes wrong
static configuration *
readConfigFile(const char *filename)
{
#ifndef NDEBUG
os::log("apitrace: attempting to read configuration file: %s\n", filename);
#endif
FILE *f = fopen(filename, "r");
if (!f)
return NULL;
os::log("apitrace: using configuration file: %s\n", filename);
configuration *conf = new configuration;
parse_file(f, conf);
fclose(f);
if (1) {
// debug
os::log("apitrace: config GL_VENDOR = %s\n", conf->vendor.c_str());
os::log("apitrace: config GL_VERSION = %s\n", conf->version.c_str());
os::log("apitrace: config GL_EXTENSIONS = %s\n", conf->extensions.c_str());
os::log("apitrace: config GL_NUM_EXTENSIONS = %d\n", conf->numExtensions);
os::log("apitrace: config GL_RENDERER = %s\n", conf->renderer.c_str());
os::log("apitrace: config GL_SHADING_LANGUAGE_VERSION = %s\n", conf->glslVersion.c_str());
os::log("apitrace: config GL_MAX_TEXTURE_SIZE = %d\n", conf->maxTextureSize);
os::log("apitrace: config GL_MAJOR_VERSION = %d\n", conf->versionMajor);
os::log("apitrace: config GL_MINOR_VERSION = %d\n", conf->versionMinor);
os::log("apitrace: config GL_CONTEXT_PROFILE_MASK = 0x%x\n", conf->profileMask);
}
return conf;
}
// Get pointer to configuration object or NULL if there was no config file.
const configuration *
getConfig(void)
{
static bool configured = false;
static configuration *config = NULL;
if (!configured) {
os::String configPath;
const char *envConfigPath = getenv("GLTRACE_CONF");
if (envConfigPath) {
configPath = envConfigPath;
} else {
configPath = os::getConfigDir();
configPath.join("apitrace");
configPath.join("gltrace.conf");
}
config = gltrace::readConfigFile(configPath);
configured = true;
}
return config;
}
// Return the named string value in the config object, or NULL.
const GLubyte *
getConfigString(const configuration *config, GLenum pname)
{
if (!config)
return NULL;
switch (pname) {
case GL_VERSION:
return (const GLubyte *) config->version.c_str();
case GL_VENDOR:
return (const GLubyte *) config->vendor.c_str();
case GL_EXTENSIONS:
return (const GLubyte *) config->extensions.c_str();
case GL_RENDERER:
return (const GLubyte *) config->renderer.c_str();
case GL_SHADING_LANGUAGE_VERSION:
return (const GLubyte *) config->glslVersion.c_str();
default:
return NULL;
}
}
// Return named indexed string value from config object, or NULL.
const GLubyte *
getConfigStringi(const configuration *config, GLenum pname, GLuint index)
{
if (!config ||
pname != GL_EXTENSIONS ||
config->extensions.empty() ||
index >= config->numExtensions) {
return NULL;
}
return (const GLubyte *) config->extensionsList[index];
}
// Return named integer value from config object, or 0.
GLint
getConfigInteger(const configuration *config, GLenum pname)
{
if (!config)
return 0;
switch (pname) {
case GL_MAJOR_VERSION:
return config->versionMajor;
case GL_MINOR_VERSION:
return config->versionMinor;
case GL_CONTEXT_PROFILE_MASK:
return config->profileMask;
case GL_MAX_TEXTURE_SIZE:
return config->maxTextureSize;
case GL_NUM_EXTENSIONS:
return config->numExtensions;
default:
return 0;
}
}
} /* namespace gltrace */