Add ktx
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
/* -*- tab-width: 4; -*- */
|
||||
/* vi: set sw=2 ts=4 expandtab: */
|
||||
|
||||
/*
|
||||
* Copyright 2017-2020 Mark Callow, github.com/MarkCallow.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @class argparser
|
||||
* @~English
|
||||
*
|
||||
* @brief parse a command argument list.
|
||||
*
|
||||
* Can't use getopt_long because it declares argv as char* const*. We
|
||||
* need const char* const* when we're parsing an embedded string. Also
|
||||
* Windows C library does not have getopt_long.
|
||||
*
|
||||
* @author Mark Callow, github.com/MarkCallow.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <iostream>
|
||||
#include "argparser.h"
|
||||
#include <regex>
|
||||
|
||||
using namespace std;
|
||||
|
||||
/*
|
||||
* Construct from a string of arguments.
|
||||
*/
|
||||
argvector::argvector(const string& sArgs)
|
||||
{
|
||||
const string sep(" \t");
|
||||
// std::regex does not support negative lookbehind assertions. Darn!
|
||||
// RE to extract argument between string start and separator.
|
||||
// - Match 0 is whole matching string including separator.
|
||||
// - Match 1 is the argument.
|
||||
// - Match 2 is an empty string or a backslash.
|
||||
// Use negative character class as it is easiest way to handle
|
||||
// utf-8 file names with non-Latin characters. $ must not be last
|
||||
// in the character class or it will be taken literally not as
|
||||
// end of string.
|
||||
// Use raw literal to avoid excess backslashes.
|
||||
regex re(R"--(^([^$\\ \t]+)(\\?)(?:[ \t]+|$))--");
|
||||
size_t pos;
|
||||
|
||||
pos = sArgs.find_first_not_of(sep);
|
||||
assert(pos != string::npos);
|
||||
|
||||
auto first = sArgs.begin() + pos;
|
||||
auto last = sArgs.end();
|
||||
bool continuation = false;
|
||||
for (smatch sm; regex_search(first, last, sm, re);) {
|
||||
bool needContinuation = false;
|
||||
#define DEBUG_REGEX 0
|
||||
#if DEBUG_REGEX
|
||||
std::cout << "prefix: " << sm.prefix() << '\n';
|
||||
std::cout << "suffix: " << sm.suffix() << '\n';
|
||||
std::cout << "match size: " << sm.size() << '\n';
|
||||
for(uint32_t i = 0; i < sm.size(); i++) {
|
||||
std::cout << "match " << i << ": " << "\"" << sm.str(i) << "\"" << '\n';
|
||||
}
|
||||
#endif
|
||||
string arg;
|
||||
// All this because std::regex does not support negative
|
||||
// lookbehind assertions.
|
||||
arg = sm.str(1);
|
||||
if (sm.str(2)[0] == '\\') {
|
||||
arg += " ";
|
||||
needContinuation = true;
|
||||
}
|
||||
if (continuation) {
|
||||
this->back() += arg;
|
||||
} else {
|
||||
push_back(arg);
|
||||
}
|
||||
continuation = needContinuation;
|
||||
first = sm.suffix().first;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Construct from an array of C strings
|
||||
*/
|
||||
argvector::argvector(int argc, const char* const* argv)
|
||||
{
|
||||
for (int i = 0; i < argc; i++) {
|
||||
push_back(argv[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Functions the same as getopt_long. See `man 3 getopt_long`.
|
||||
*/
|
||||
int
|
||||
argparser::getopt(string* shortopts, const struct option* longopts,
|
||||
int* /*longindex*/)
|
||||
{
|
||||
if (optind == argv.size())
|
||||
return -1;
|
||||
|
||||
string arg;
|
||||
arg = argv[optind];
|
||||
if (arg[0] != '-' || (arg[0] == '-' && arg.size() == 1))
|
||||
return -1;
|
||||
optind++;
|
||||
|
||||
int retval = '?';
|
||||
if (arg.compare(0, 2, "--") == 0) {
|
||||
if (arg.size() == 2) return -1; // " -- " separates options and files
|
||||
const struct option* opt = longopts;
|
||||
while (opt->name != nullptr) {
|
||||
if (arg.compare(2, string::npos, opt->name) == 0) {
|
||||
retval = opt->val;
|
||||
if (opt->has_arg != option::no_argument) {
|
||||
if (optind >= argv.size() || (optarg = argv[optind++])[0] == '-') {
|
||||
optarg.clear();
|
||||
optind--;
|
||||
if (opt->has_arg == option::required_argument)
|
||||
retval = ':';
|
||||
}
|
||||
}
|
||||
if (opt->flag != nullptr) {
|
||||
*opt->flag = opt->val;
|
||||
retval = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
opt++;
|
||||
}
|
||||
} else if (shortopts != nullptr && arg.compare(0, 1, "-") == 0) {
|
||||
size_t pos = shortopts->find(arg.substr(1, 1));
|
||||
if (pos != string::npos) {
|
||||
retval = (*shortopts)[pos];
|
||||
if (pos < shortopts->length()
|
||||
&& ((*shortopts)[++pos] == ':' || (*shortopts)[pos] == ';')) {
|
||||
if (optind >= argv.size() || (optarg = argv[optind++])[0] == '-') {
|
||||
optarg.clear();
|
||||
optind--;
|
||||
if ((*shortopts)[pos] == ':') // required argument
|
||||
retval = ':';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
//================== Helper for apps' processArgs ========================
|
||||
|
||||
std::istream& operator >> (std::istream& stream, const skip& x)
|
||||
{
|
||||
std::ios_base::fmtflags f = stream.flags();
|
||||
stream >> std::noskipws;
|
||||
|
||||
char c;
|
||||
const char* text = x.text;
|
||||
while (stream && *text++)
|
||||
stream >> c;
|
||||
|
||||
stream.flags(f);
|
||||
return stream;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/* -*- tab-width: 4; -*- */
|
||||
/* vi: set sw=2 ts=4 expandtab: */
|
||||
|
||||
#ifndef ARGPARSER_H
|
||||
#define ARGPARSER_H
|
||||
|
||||
/*
|
||||
* Copyright 2017-2020 Mark Callow, github.com/MarkCallow.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <ios>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class argvector : public std::vector<std::string> {
|
||||
public:
|
||||
argvector() { };
|
||||
argvector(const std::string& argstring);
|
||||
argvector(int argc, const char* const* argv);
|
||||
};
|
||||
|
||||
class argparser {
|
||||
public:
|
||||
struct option {
|
||||
const char* name;
|
||||
enum has_arg_t {no_argument, required_argument, optional_argument} has_arg;
|
||||
int* flag;
|
||||
int val;
|
||||
|
||||
option(const char* name, has_arg_t has_arg, int* flag, int val) : name(name), has_arg(has_arg), flag(flag), val(val) {}
|
||||
};
|
||||
|
||||
std::string optarg;
|
||||
unsigned int optind;
|
||||
argvector argv;
|
||||
|
||||
argparser(argvector& argv, unsigned int startindex = 0)
|
||||
: optind(startindex), argv(argv) { }
|
||||
|
||||
argparser(int argc, const char* const* argv1)
|
||||
: optind(1), argv(argc, argv1) { }
|
||||
|
||||
int getopt(std::string* shortopts, const struct option* longopts,
|
||||
int* longindex = nullptr);
|
||||
};
|
||||
|
||||
//================== Helper for apps' processArgs ========================
|
||||
|
||||
// skips the number of characters equal to the length of given text
|
||||
// does not check whether the skipped characters are the same as it
|
||||
struct skip
|
||||
{
|
||||
const char* text;
|
||||
skip(const char* text) : text(text) {}
|
||||
};
|
||||
|
||||
std::istream& operator >> (std::istream& stream, const skip& x);
|
||||
|
||||
#endif /* ARGPARSER_H */
|
||||
@@ -0,0 +1,472 @@
|
||||
// -*- tab-width: 4; -*-
|
||||
// vi: set sw=2 ts=4 sts=4 expandtab:
|
||||
|
||||
// Copyright 2019-2020 The Khronos Group Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
#if defined (_WIN32)
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#define WINDOWS_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#include <stdarg.h>
|
||||
#if (_MSVC_LANG >= 201703L || __cplusplus >= 201703L)
|
||||
#include <algorithm>
|
||||
#endif
|
||||
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <ktx.h>
|
||||
|
||||
#include "argparser.h"
|
||||
#include "platform_utils.h"
|
||||
|
||||
#define QUOTE(x) #x
|
||||
#define STR(x) QUOTE(x)
|
||||
|
||||
// Thanks Windows!!!
|
||||
#if defined(min)
|
||||
#undef min
|
||||
#endif
|
||||
#if defined(max)
|
||||
#undef max
|
||||
#endif
|
||||
|
||||
using namespace std;
|
||||
|
||||
// clamp is in std:: from c++17.
|
||||
#if !(_MSVC_LANG >= 201703L || __cplusplus >= 201703L)
|
||||
template <typename T> inline T clamp(T value, T low, T high) {
|
||||
return (value < low) ? low : ((value > high) ? high : value);
|
||||
}
|
||||
#endif
|
||||
|
||||
template<typename T>
|
||||
struct clamped
|
||||
{
|
||||
clamped(T def_v, T min_v, T max_v) :
|
||||
def(def_v),
|
||||
min(min_v),
|
||||
max(max_v),
|
||||
value(def_v)
|
||||
{
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
value = def;
|
||||
}
|
||||
|
||||
operator T() const
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
T operator= (T v)
|
||||
{
|
||||
value = clamp<T>(v, min, max);
|
||||
return value;
|
||||
}
|
||||
|
||||
T def;
|
||||
T min;
|
||||
T max;
|
||||
T value;
|
||||
};
|
||||
|
||||
/**
|
||||
//! [ktxApp options]
|
||||
<dl>
|
||||
<dt>-h, \--help</dt>
|
||||
<dd>Print this usage message and exit.</dd>
|
||||
<dt>-v, \--version</dt>
|
||||
<dd>Print the version number of this program and exit.</dd>
|
||||
</dl>
|
||||
|
||||
//! [ktxApp options]
|
||||
*/
|
||||
|
||||
class ktxApp {
|
||||
public:
|
||||
virtual int main(int argc, char* argv[]) = 0;
|
||||
virtual void usage() {
|
||||
cerr <<
|
||||
" -h, --help Print this usage message and exit.\n"
|
||||
" -v, --version Print the version number of this program and exit.\n"
|
||||
#if defined(_WIN32) && defined(DEBUG)
|
||||
" --ld Launch Visual Studio deugger at start up.\n"
|
||||
#endif
|
||||
;
|
||||
};
|
||||
string& getName() { return name; }
|
||||
|
||||
protected:
|
||||
struct commandOptions {
|
||||
std::vector<string> infiles;
|
||||
string outfile;
|
||||
int test;
|
||||
int warn;
|
||||
int launchDebugger;
|
||||
|
||||
commandOptions() : test(false), warn(1), launchDebugger(0) { }
|
||||
};
|
||||
|
||||
ktxApp(std::string& version, std::string& defaultVersion,
|
||||
commandOptions& options)
|
||||
: version(version), defaultVersion(defaultVersion),
|
||||
options(options) { }
|
||||
|
||||
void error(const char *pFmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, pFmt);
|
||||
|
||||
cerr << name << ": ";
|
||||
vfprintf(stderr, pFmt, args);
|
||||
va_end(args);
|
||||
cerr << "\n";
|
||||
}
|
||||
|
||||
void warning(const char *pFmt, va_list args) {
|
||||
if (options.warn) {
|
||||
cerr << name << " warning! ";
|
||||
vfprintf(stderr, pFmt, args);
|
||||
cerr << endl;
|
||||
}
|
||||
}
|
||||
|
||||
void warning(const char *pFmt, ...) {
|
||||
if (options.warn) {
|
||||
va_list args;
|
||||
va_start(args, pFmt);
|
||||
|
||||
warning(pFmt, args);
|
||||
cerr << endl;
|
||||
}
|
||||
}
|
||||
|
||||
void warning(const string& msg) {
|
||||
if (options.warn) {
|
||||
cerr << name << " warning! ";
|
||||
cerr << msg << endl;
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal
|
||||
* @~English
|
||||
* @brief Open a file for writing, failing if it exists.
|
||||
*
|
||||
* Assumes binary mode is wanted.
|
||||
*
|
||||
* Works around an annoying limitation of the VS2013-era msvcrt's
|
||||
* @c fopen that implements an early version of the @c fopen spec.
|
||||
* that does not accept 'x' as a mode character. For some reason
|
||||
* Mingw uses this ancient version. Rather than use ifdef heuristics
|
||||
* to identify sufferers of the limitation, it handles the error case
|
||||
* and uses an alternate way to check for file existence.
|
||||
*
|
||||
* @return A stdio FILE* for the created file. If the file already exists
|
||||
* returns nullptr and sets errno to EEXIST.
|
||||
*/
|
||||
static FILE* fopen_write_if_not_exists(const string& path) {
|
||||
FILE* file = ::fopenUTF8(path, "wxb");
|
||||
if (!file && errno == EINVAL) {
|
||||
file = ::fopenUTF8(path, "r");
|
||||
if (file) {
|
||||
fclose(file);
|
||||
file = nullptr;
|
||||
errno = EEXIST;
|
||||
} else {
|
||||
file = ::fopenUTF8(path, "wb");
|
||||
}
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
int strtoi(const char* str)
|
||||
{
|
||||
char* endptr;
|
||||
int value = (int)strtol(str, &endptr, 0);
|
||||
// Some implementations set errno == EINVAL but we can't rely on it.
|
||||
if (value == 0 && endptr && *endptr != '\0') {
|
||||
cerr << "Argument \"" << endptr << "\" not a number." << endl;
|
||||
usage();
|
||||
exit(1);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
enum StdinUse { eDisallowStdin, eAllowStdin };
|
||||
enum OutfilePos { eNone, eFirst, eLast };
|
||||
void processCommandLine(int argc, char* argv[],
|
||||
StdinUse stdinStat = eAllowStdin,
|
||||
OutfilePos outfilePos = eNone)
|
||||
{
|
||||
uint32_t i;
|
||||
size_t slash, dot;
|
||||
|
||||
name = argv[0];
|
||||
// For consistent Id, only use the stem of name;
|
||||
slash = name.find_last_of('\\');
|
||||
if (slash == string::npos)
|
||||
slash = name.find_last_of('/');
|
||||
if (slash != string::npos)
|
||||
name.erase(0, slash+1); // Remove directory name.
|
||||
dot = name.find_last_of('.');
|
||||
if (dot != string::npos)
|
||||
name.erase(dot, string::npos); // Remove extension.
|
||||
|
||||
argparser parser(argc, argv);
|
||||
processOptions(parser);
|
||||
|
||||
i = parser.optind;
|
||||
if (argc - i > 0) {
|
||||
if (outfilePos == eFirst)
|
||||
options.outfile = parser.argv[i++];
|
||||
uint32_t infileCount = outfilePos == eLast ? argc - 1 : argc;
|
||||
for (; i < infileCount; i++) {
|
||||
if (parser.argv[i][0] == '@') {
|
||||
if (!loadFileList(parser.argv[i],
|
||||
parser.argv[i][1] == '@',
|
||||
options.infiles)) {
|
||||
exit(1);
|
||||
}
|
||||
} else {
|
||||
options.infiles.push_back(parser.argv[i]);
|
||||
}
|
||||
}
|
||||
if (options.infiles.size() > 1) {
|
||||
std::vector<string>::const_iterator it;
|
||||
for (it = options.infiles.begin(); it < options.infiles.end(); it++) {
|
||||
if (it->compare("-") == 0) {
|
||||
error("cannot use stdin as one among many inputs.");
|
||||
usage();
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (outfilePos == eLast)
|
||||
options.outfile = parser.argv[i];
|
||||
}
|
||||
|
||||
if (options.infiles.size() == 0) {
|
||||
if (stdinStat == eAllowStdin) {
|
||||
options.infiles.push_back("-"); // Use stdin as 0 files.
|
||||
} else {
|
||||
error("need some input files.");
|
||||
usage();
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
if (outfilePos != eNone && options.outfile.empty()) {
|
||||
error("need an output file");
|
||||
}
|
||||
}
|
||||
|
||||
bool loadFileList(const string &f, bool relativize,
|
||||
vector<string>& filenames)
|
||||
{
|
||||
string listName(f);
|
||||
listName.erase(0, relativize ? 2 : 1);
|
||||
|
||||
FILE *lf = nullptr;
|
||||
lf = fopenUTF8(listName, "r");
|
||||
if (!lf) {
|
||||
error("failed opening filename list: \"%s\": %s\n",
|
||||
listName.c_str(), strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
string dirname;
|
||||
|
||||
if (relativize) {
|
||||
size_t dirnameEnd = listName.find_last_of('/');
|
||||
if (dirnameEnd == string::npos) {
|
||||
relativize = false;
|
||||
} else {
|
||||
dirname = listName.substr(0, dirnameEnd + 1);
|
||||
}
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
// Cross platform PATH_MAX def is too much trouble!
|
||||
char buf[4096];
|
||||
buf[0] = '\0';
|
||||
|
||||
char *p = fgets(buf, sizeof(buf), lf);
|
||||
if (!p) {
|
||||
if (ferror(lf)) {
|
||||
error("failed reading filename list: \"%s\": %s\n",
|
||||
listName.c_str(), strerror(errno));
|
||||
fclose(lf);
|
||||
return false;
|
||||
} else
|
||||
break;
|
||||
}
|
||||
|
||||
string readFilename(p);
|
||||
while (readFilename.size()) {
|
||||
if (readFilename[0] == ' ')
|
||||
readFilename.erase(0, 1);
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
while (readFilename.size()) {
|
||||
const char c = readFilename.back();
|
||||
if ((c == ' ') || (c == '\n') || (c == '\r'))
|
||||
readFilename.erase(readFilename.size() - 1, 1);
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
if (readFilename.size()) {
|
||||
if (relativize)
|
||||
filenames.push_back(dirname + readFilename);
|
||||
else
|
||||
filenames.push_back(readFilename);
|
||||
}
|
||||
}
|
||||
|
||||
fclose(lf);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void processOptions(argparser& parser) {
|
||||
int opt;
|
||||
while ((opt = parser.getopt(&short_opts, option_list.data(), NULL)) != -1) {
|
||||
switch (opt) {
|
||||
case 0:
|
||||
break;
|
||||
case 10000:
|
||||
break;
|
||||
case 'h':
|
||||
usage();
|
||||
exit(0);
|
||||
case 'v':
|
||||
printVersion();
|
||||
exit(0);
|
||||
case ':':
|
||||
error("missing required option argument.");
|
||||
usage();
|
||||
exit(0);
|
||||
default:
|
||||
if (!processOption(parser, opt)) {
|
||||
usage();
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
#if defined(_WIN32) && defined(DEBUG)
|
||||
if (options.launchDebugger)
|
||||
launchDebugger();
|
||||
#endif
|
||||
}
|
||||
|
||||
virtual bool processOption(argparser& parser, int opt) = 0;
|
||||
|
||||
void writeId(std::ostream& dst, bool chktest) {
|
||||
dst << name << " ";
|
||||
dst << (!chktest || !options.test ? version : defaultVersion);
|
||||
}
|
||||
|
||||
void printVersion() {
|
||||
writeId(cerr, false);
|
||||
cerr << endl;
|
||||
}
|
||||
|
||||
#if defined(_WIN32) && defined(DEBUG)
|
||||
// For use when debugging stdin with Visual Studio which does not have a
|
||||
// "wait for executable to be launched" choice in its debugger settings.
|
||||
bool launchDebugger()
|
||||
{
|
||||
// Get System directory, typically c:\windows\system32
|
||||
std::wstring systemDir(MAX_PATH + 1, '\0');
|
||||
UINT nChars = GetSystemDirectoryW(&systemDir[0],
|
||||
static_cast<UINT>(systemDir.length()));
|
||||
if (nChars == 0) return false; // failed to get system directory
|
||||
systemDir.resize(nChars);
|
||||
|
||||
// Get process ID and create the command line
|
||||
DWORD pid = GetCurrentProcessId();
|
||||
std::wostringstream s;
|
||||
s << systemDir << L"\\vsjitdebugger.exe -p " << pid;
|
||||
std::wstring cmdLine = s.str();
|
||||
|
||||
// Start debugger process
|
||||
STARTUPINFOW si;
|
||||
ZeroMemory(&si, sizeof(si));
|
||||
si.cb = sizeof(si);
|
||||
|
||||
PROCESS_INFORMATION pi;
|
||||
ZeroMemory(&pi, sizeof(pi));
|
||||
|
||||
if (!CreateProcessW(NULL, &cmdLine[0], NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) return false;
|
||||
|
||||
// Close debugger process handles to eliminate resource leak
|
||||
CloseHandle(pi.hThread);
|
||||
CloseHandle(pi.hProcess);
|
||||
|
||||
// Wait for the debugger to attach
|
||||
while (!IsDebuggerPresent()) Sleep(100);
|
||||
|
||||
// Stop execution so the debugger can take over
|
||||
DebugBreak();
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
string name;
|
||||
string& version;
|
||||
string& defaultVersion;
|
||||
|
||||
commandOptions& options;
|
||||
|
||||
virtual void validateOptions() { }
|
||||
|
||||
std::vector<argparser::option> option_list {
|
||||
{ "help", argparser::option::no_argument, NULL, 'h' },
|
||||
{ "version", argparser::option::no_argument, NULL, 'v' },
|
||||
{ "test", argparser::option::no_argument, &options.test, 1},
|
||||
#if defined(_WIN32) && defined(DEBUG)
|
||||
{ "ld", argparser::option::no_argument, &options.launchDebugger, 1},
|
||||
#endif
|
||||
// -NSDocumentRevisionsDebugMode YES is appended to the end
|
||||
// of the command by Xcode when debugging and "Allow debugging when
|
||||
// using document Versions Browser" is checked in the scheme. It
|
||||
// defaults to checked and is saved in a user-specific file not the
|
||||
// pbxproj file so it can't be disabled in a generated project.
|
||||
// Remove these from the arguments under consideration.
|
||||
{ "-NSDocumentRevisionsDebugMode", argparser::option::required_argument, NULL, 10000 },
|
||||
{ nullptr, argparser::option::no_argument, nullptr, 0 }
|
||||
};
|
||||
|
||||
string short_opts = "hv";
|
||||
};
|
||||
|
||||
extern ktxApp& theApp;
|
||||
|
||||
/** @internal
|
||||
* @~English
|
||||
* @brief Common main for all derived classes.
|
||||
*
|
||||
* Handles rewriting of argv to UTF-8 on Windows.
|
||||
* Each app needs to initialize @c theApp to
|
||||
* point to an instance of itself.
|
||||
*/
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
InitUTF8CLI(argc, argv);
|
||||
#if 0
|
||||
if (!SetConsoleOutputCP(CP_UTF8)) {
|
||||
cerr << theApp.getName() << "warning: failed to set UTF-8 code page for console output."
|
||||
<< endl;
|
||||
}
|
||||
#endif
|
||||
return theApp.main(argc, argv);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
// Copyright 2022-2023 The Khronos Group Inc.
|
||||
// Copyright 2022-2023 RasterGrid Kft.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "stdafx.h"
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#include <shellapi.h>
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32) && !defined(_UNICODE)
|
||||
// For Windows, we convert the UTF-8 path to a UTF-16 path to force using
|
||||
// the APIs that correctly handle unicode characters.
|
||||
inline std::wstring DecodeUTF8Path(std::string path) {
|
||||
std::wstring result;
|
||||
int len = MultiByteToWideChar(CP_UTF8, 0, path.c_str(), static_cast<int>(path.length()), NULL, 0);
|
||||
if (len > 0)
|
||||
{
|
||||
result.resize(len);
|
||||
MultiByteToWideChar(CP_UTF8, 0, path.c_str(), static_cast<int>(path.length()), &result[0], len);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
#else
|
||||
// For other platforms there is no need for any conversion, they
|
||||
// support UTF-8 natively.
|
||||
inline std::string DecodeUTF8Path(std::string path) {
|
||||
return path;
|
||||
}
|
||||
#endif
|
||||
|
||||
inline void InitUTF8CLI(int& argc, char* argv[]) {
|
||||
#if defined(_WIN32)
|
||||
// Windows does not support UTF-8 argv so we have to manually acquire it
|
||||
static std::vector<std::unique_ptr<char[]>> utf8Argv(argc);
|
||||
LPWSTR commandLine = GetCommandLineW();
|
||||
LPWSTR* wideArgv = CommandLineToArgvW(commandLine, &argc);
|
||||
for (int i = 0; i < argc; ++i) {
|
||||
int byteSize = WideCharToMultiByte(CP_UTF8, 0, wideArgv[i], -1, nullptr, 0, nullptr, nullptr);
|
||||
utf8Argv[i] = std::make_unique<char[]>(byteSize);
|
||||
WideCharToMultiByte(CP_UTF8, 0, wideArgv[i], -1, utf8Argv[i].get(), byteSize, nullptr, nullptr);
|
||||
argv[i] = utf8Argv[i].get();
|
||||
}
|
||||
#else
|
||||
// Nothing to do for other platforms
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
#endif
|
||||
}
|
||||
|
||||
inline FILE* fopenUTF8(const std::string& path, const std::string& mode) {
|
||||
#if defined(_WIN32)
|
||||
FILE* fp;
|
||||
// Returned errno_t value is also set in the global errno.
|
||||
(void)_wfopen_s(&fp, DecodeUTF8Path(path).c_str(), DecodeUTF8Path(mode).c_str());
|
||||
return fp;
|
||||
#else
|
||||
return fopen(path.c_str(), mode.c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
inline int unlinkUTF8(const std::string& path) {
|
||||
#if defined(_WIN32)
|
||||
return _wunlink(DecodeUTF8Path(path).c_str());
|
||||
#else
|
||||
return unlink(path.c_str());
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
/* -*- tab-width: 4; -*- */
|
||||
/* vi: set sw=2 ts=4 expandtab: */
|
||||
|
||||
/*
|
||||
* Copyright 2021 Paolo Jovon.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @file
|
||||
* @~English
|
||||
*
|
||||
* @brief A ktxStream that wraps a C++ std::streambuf.
|
||||
*
|
||||
* Modified from Paolo's original to make it a template so that the reference to std::streambuf can
|
||||
* be passed either as a `unique_ptr` or a regular pointer. As our use is to pass the streambuf from
|
||||
* an istream, potentially that from std::cin, or an istringstream that is not being not allocated by `new`
|
||||
* we can't use a `unique_ptr`. i.e. can't attempt to delete the stringbuf when done. Also the
|
||||
* destructor now destructs the stream, if not already destructed.
|
||||
*
|
||||
* @author Paolo Jovon
|
||||
* @author Mark Callow
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <ktx.h>
|
||||
|
||||
static std::ostream cnull(0);
|
||||
static std::ostream& logstream = cnull;
|
||||
//static std::ostream logstream = std::cerr;
|
||||
|
||||
/// @brief Template for a ktxStream that wraps a C++ std::streambuf.
|
||||
///
|
||||
/// The template supports 2 ways of referencing the underlying std::streambuf:
|
||||
/// as a @c unique_ptr or as a regular pointer. For the former case, the
|
||||
/// @c unique_ptr std::streambuf and StreambufStream should be created
|
||||
/// like this:
|
||||
/// @code
|
||||
/// // Can use stringbuf or any class derived from it.
|
||||
/// auto filebuf = std::make_unique<std::filebuf>();
|
||||
/// StreambufStream<std::unique_ptr<std::streambuf>>ktxStream(
|
||||
/// std::move(filebuf),
|
||||
/// ios::in);
|
||||
/// @endcode
|
||||
///
|
||||
///
|
||||
template <typename T>
|
||||
class StreambufStream
|
||||
{
|
||||
// Doubt this will ever get triggered
|
||||
static_assert(sizeof(char) == sizeof(uint8_t),
|
||||
"Chars are != 1 byte in this platform");
|
||||
|
||||
public:
|
||||
StreambufStream(T streambuf,
|
||||
std::ios::openmode seek_mode = std::ios::in | std::ios::out)
|
||||
: _streambuf{std::move(streambuf)}
|
||||
, _seek_mode{seek_mode}
|
||||
, _stream{std::make_unique<ktxStream>()}
|
||||
, _destructed{false}
|
||||
{
|
||||
initialize_stream();
|
||||
}
|
||||
|
||||
StreambufStream(const StreambufStream&) = delete;
|
||||
StreambufStream &operator=(const StreambufStream&) = delete;
|
||||
|
||||
StreambufStream(StreambufStream&&) = delete;
|
||||
StreambufStream &operator=(StreambufStream&&) = delete;
|
||||
|
||||
virtual ~StreambufStream()
|
||||
{
|
||||
if (!_destructed)
|
||||
stream()->destruct(stream());
|
||||
}
|
||||
|
||||
inline ktxStream* stream() const
|
||||
{
|
||||
return _stream.get();
|
||||
}
|
||||
|
||||
std::streambuf* streambuf() const;
|
||||
|
||||
inline std::ios::openmode seek_mode() const
|
||||
{
|
||||
return _seek_mode;
|
||||
}
|
||||
|
||||
inline void seek_mode(std::ios::openmode newmode)
|
||||
{
|
||||
_seek_mode = newmode;
|
||||
}
|
||||
|
||||
inline bool destructed() const
|
||||
{
|
||||
return _destructed;
|
||||
}
|
||||
|
||||
protected:
|
||||
void initialize_stream() {
|
||||
_stream->type = eStreamTypeCustom;
|
||||
_stream->closeOnDestruct = false;
|
||||
|
||||
auto& custom_ptr = _stream->data.custom_ptr;
|
||||
custom_ptr.address = this;
|
||||
custom_ptr.allocatorAddress = nullptr; // N/A
|
||||
custom_ptr.size = 0; // N/A
|
||||
|
||||
_stream->read = read;
|
||||
_stream->skip = skip;
|
||||
_stream->write = write;
|
||||
_stream->getpos = getpos;
|
||||
_stream->setpos = setpos;
|
||||
_stream->getsize = getsize;
|
||||
_stream->destruct = destruct;
|
||||
}
|
||||
|
||||
// C++ streambuf overrides
|
||||
|
||||
// ktxStream vtable implementations
|
||||
|
||||
inline static StreambufStream* parent(ktxStream *str)
|
||||
{
|
||||
return reinterpret_cast<StreambufStream*>(str->data.custom_ptr.address);
|
||||
}
|
||||
|
||||
static KTX_error_code read(ktxStream* str, void* dst, ktx_size_t count)
|
||||
{
|
||||
auto self = parent(str);
|
||||
if (count == 0)
|
||||
{
|
||||
return KTX_SUCCESS;
|
||||
}
|
||||
logstream << "\t read: " << count << 'B' << std::endl;
|
||||
|
||||
const auto stdcount = std::streamsize(count);
|
||||
const std::streamsize nread = self->_streambuf->sgetn(reinterpret_cast<char*>(dst), stdcount);
|
||||
return (nread == stdcount) ? KTX_SUCCESS : KTX_FILE_UNEXPECTED_EOF;
|
||||
}
|
||||
|
||||
static KTX_error_code skip(ktxStream* str, ktx_size_t count)
|
||||
{
|
||||
auto self = parent(str);
|
||||
if (count == 0)
|
||||
{
|
||||
return KTX_SUCCESS;
|
||||
}
|
||||
logstream << "\t skip: " << count << 'B' << std::endl;
|
||||
|
||||
const std::streampos curpos = self->_streambuf->pubseekoff(0, std::ios::cur, self->_seek_mode);
|
||||
const std::streampos newpos = self->_streambuf->pubseekoff(std::streamoff(count), std::ios::cur, self->_seek_mode);
|
||||
return (curpos > newpos) ? KTX_SUCCESS : KTX_FILE_SEEK_ERROR;
|
||||
}
|
||||
|
||||
static KTX_error_code write(ktxStream* str, const void* src, ktx_size_t size, ktx_size_t count)
|
||||
{
|
||||
auto self = parent(str);
|
||||
if (size == 0 || count == 0)
|
||||
{
|
||||
return KTX_SUCCESS;
|
||||
}
|
||||
logstream << "\t write: " << count << "*" << size << "B" << std::endl;
|
||||
|
||||
const auto ntotal = std::streamsize(size * count);
|
||||
const std::streamsize nput = self->_streambuf->sputn(reinterpret_cast<const char*>(src), ntotal);
|
||||
return (nput == ntotal) ? KTX_SUCCESS : KTX_FILE_WRITE_ERROR;
|
||||
}
|
||||
|
||||
static KTX_error_code getpos(ktxStream* str, ktx_off_t *offset)
|
||||
{
|
||||
auto self = parent(str);
|
||||
*offset = ktx_off_t(self->_streambuf->pubseekoff(0, std::ios::cur, self->_seek_mode));
|
||||
logstream << "\tgetpos: " << *offset << std::endl;
|
||||
return KTX_SUCCESS;
|
||||
}
|
||||
|
||||
static KTX_error_code setpos(ktxStream* str, ktx_off_t offset)
|
||||
{
|
||||
auto self = parent(str);
|
||||
const auto newpos = std::streamoff(offset);
|
||||
const std::streampos setpos = self->_streambuf->pubseekoff(newpos, std::ios::beg, self->_seek_mode);
|
||||
logstream << "\tsetpos: " << offset << std::endl;
|
||||
return (setpos == newpos) ? KTX_SUCCESS : KTX_FILE_SEEK_ERROR;
|
||||
}
|
||||
|
||||
static KTX_error_code getsize(ktxStream* str, ktx_size_t* size)
|
||||
{
|
||||
auto self = parent(str);
|
||||
const std::streampos oldpos = self->_streambuf->pubseekoff(0, std::ios::cur, self->_seek_mode);
|
||||
*size = ktx_size_t(self->_streambuf->pubseekoff(0, std::ios::end));
|
||||
const std::streampos newpos = self->_streambuf->pubseekoff(oldpos, std::ios::beg, self->_seek_mode);
|
||||
logstream << "\t size: " << *size << 'B' << std::endl;
|
||||
return (oldpos == newpos) ? KTX_SUCCESS : KTX_FILE_SEEK_ERROR;
|
||||
}
|
||||
|
||||
static void destruct(ktxStream* str)
|
||||
{
|
||||
auto self = parent(str);
|
||||
self->_destructed = true;
|
||||
}
|
||||
|
||||
T _streambuf;
|
||||
std::ios::openmode _seek_mode;
|
||||
std::unique_ptr<ktxStream> _stream;
|
||||
// ktxTexture?_CreateFromStream destructs the ktxStream when finished, if
|
||||
// KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT was passed. This variable tracks
|
||||
// if the ktxStream's destructor has been called.
|
||||
bool _destructed;
|
||||
};
|
||||
|
||||
// I have not yet found a way to do this inside the template definition.
|
||||
// However `inline` should prevent any multiple definition errors.
|
||||
template<>
|
||||
inline std::streambuf* StreambufStream<std::streambuf*>::streambuf() const
|
||||
{
|
||||
return _streambuf;
|
||||
}
|
||||
|
||||
template<>
|
||||
inline std::streambuf* StreambufStream<std::unique_ptr<std::streambuf>>::streambuf() const
|
||||
{
|
||||
return _streambuf.get();
|
||||
}
|
||||
+1154
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,28 @@
|
||||
// Copyright 2010-2020 The Khronos Group Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// stdafx.h
|
||||
|
||||
#pragma once
|
||||
|
||||
#if defined(_WIN32)
|
||||
// _CRT_SECURE_NO_WARNINGS must be defined before <windows.h>,
|
||||
// <stdio.h> and and <iostream>
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#ifdef _WIN32
|
||||
#include <io.h>
|
||||
#if _MSC_VER < 1900
|
||||
#define snprintf _snprintf
|
||||
#endif
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#define _setmode(x, y) 0
|
||||
#endif
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
@@ -0,0 +1,37 @@
|
||||
/* -*- tab-width: 4; -*- */
|
||||
/* vi: set sw=2 ts=4 expandtab: */
|
||||
|
||||
/* Copyright 2019-2018 The Khronos Group Inc.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/* I'm extending this beyond the purpose implied by its name rather than creating
|
||||
* a new file to hold the FALLTHROUGH declaration as this
|
||||
* file is already included in most places FALLTHROUGH
|
||||
* is needed.
|
||||
*/
|
||||
|
||||
#ifndef _UNUSED_H
|
||||
#define _UNUSED_H
|
||||
|
||||
#if (__cplusplus >= 201703L)
|
||||
#define MAYBE_UNUSED [[maybe_unused]]
|
||||
#elif __GNUC__ || __clang__
|
||||
#define MAYBE_UNUSED __attribute__((unused))
|
||||
#else
|
||||
// Boohoo. VC++ has no equivalent
|
||||
#define MAYBE_UNUSED
|
||||
#endif
|
||||
|
||||
#define U_ASSERT_ONLY MAYBE_UNUSED
|
||||
|
||||
// For unused parameters of c functions. Portable.
|
||||
#define UNUSED(x) (void)(x)
|
||||
|
||||
#if !__clang__ && __GNUC__ // grumble ... clang ... grumble
|
||||
#define FALLTHROUGH __attribute__((fallthrough))
|
||||
#else
|
||||
#define FALLTHROUGH
|
||||
#endif
|
||||
|
||||
#endif /* UNUSED_H */
|
||||
Reference in New Issue
Block a user