bgfx/tools/shaderc.cpp

795 lines
17 KiB
C++

/*
* Copyright 2011-2012 Branimir Karadzic. All rights reserved.
* License: http://www.opensource.org/licenses/BSD-2-Clause
*/
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <vector>
#include "glsl_optimizer.h"
#define MAX_TAGS 256
extern "C" {
# include <fpp.h>
} // extern "C"
#if 0
# define BX_TRACE(_format, ...) fprintf(stderr, "" _format "\n", ##__VA_ARGS__)
#endif // DEBUG
#define BX_NAMESPACE 1
#include <bx/bx.h>
#if BX_PLATFORM_LINUX
# define _stricmp strcasecmp
#endif // BX_PLATFORM_LINUX
#include <bx/commandline.h>
#include <bx/countof.h>
#include <bx/endian.h>
#include <bx/uint32_t.h>
#if BX_PLATFORM_WINDOWS
# include <d3dx9.h>
#endif // BX_PLATFORM_WINDOWS
long int fsize(FILE* _file)
{
long int pos = ftell(_file);
fseek(_file, 0L, SEEK_END);
long int size = ftell(_file);
fseek(_file, pos, SEEK_SET);
return size;
}
struct ConstantType
{
enum Enum
{
Uniform1i,
Uniform1f,
End,
Uniform1iv,
Uniform1fv,
Uniform2fv,
Uniform3fv,
Uniform4fv,
Uniform3x3fv,
Uniform4x4fv,
Count,
TypeMask = 0x7f,
FragmentBit = 0x80
};
};
static const char* s_constantTypeName[ConstantType::Count] =
{
"int",
"float",
NULL,
"int",
"float",
"float2",
"float3",
"float4",
"float3x3",
"float4x4",
};
struct Uniform
{
std::string name;
ConstantType::Enum type;
uint8_t num;
uint16_t regIndex;
uint16_t regCount;
};
typedef std::vector<Uniform> UniformArray;
#if BX_PLATFORM_WINDOWS
struct ConstRemap
{
ConstantType::Enum id;
D3DXPARAMETER_CLASS paramClass;
D3DXPARAMETER_TYPE paramType;
uint32_t paramBytes;
};
static const ConstRemap s_constRemap[7] =
{
{ ConstantType::Uniform1iv, D3DXPC_SCALAR, D3DXPT_INT, 4 },
{ ConstantType::Uniform1fv, D3DXPC_SCALAR, D3DXPT_FLOAT, 4 },
{ ConstantType::Uniform2fv, D3DXPC_VECTOR, D3DXPT_FLOAT, 8 },
{ ConstantType::Uniform3fv, D3DXPC_VECTOR, D3DXPT_FLOAT, 12 },
{ ConstantType::Uniform4fv, D3DXPC_VECTOR, D3DXPT_FLOAT, 16 },
{ ConstantType::Uniform3x3fv, D3DXPC_MATRIX_COLUMNS, D3DXPT_FLOAT, 36 },
{ ConstantType::Uniform4x4fv, D3DXPC_MATRIX_COLUMNS, D3DXPT_FLOAT, 64 },
};
ConstantType::Enum findConstantType(const D3DXCONSTANT_DESC& constDesc)
{
uint32_t count = sizeof(s_constRemap)/sizeof(ConstRemap);
for (uint32_t ii = 0; ii < count; ++ii)
{
const ConstRemap& remap = s_constRemap[ii];
if (remap.paramClass == constDesc.Class
&& remap.paramType == constDesc.Type
&& (constDesc.Bytes%remap.paramBytes) == 0)
{
return remap.id;
}
}
return ConstantType::Count;
}
static uint32_t s_optimizationLevel[4] =
{
D3DXSHADER_OPTIMIZATION_LEVEL0,
D3DXSHADER_OPTIMIZATION_LEVEL1,
D3DXSHADER_OPTIMIZATION_LEVEL2,
D3DXSHADER_OPTIMIZATION_LEVEL3,
};
#endif // BX_PLATFORM_WINDOWS
class Stream
{
public:
Stream(FILE* _file, bool _bigEndian = false)
: m_file(_file)
, m_bigEndian(_bigEndian)
{
}
~Stream()
{
}
void close()
{
m_file = NULL;
}
void writef(const char* _format, ...)
{
if (NULL != m_file)
{
va_list argList;
va_start(argList, _format);
char temp[2048];
int len = vsnprintf(temp, sizeof(temp), _format, argList);
fwrite(temp, len, 1, m_file);
va_end(argList);
}
}
void write(const char* _str)
{
if (NULL != m_file)
{
fwrite(_str, strlen(_str), 1, m_file);
}
}
void write(const void* _data, size_t _size)
{
if (NULL != m_file)
{
fwrite(_data, _size, 1, m_file);
}
}
template<typename Ty>
void write(Ty _value)
{
Ty temp = m_bigEndian ? bx::bigEndian<Ty>(_value) : _value;
write(&temp, sizeof(Ty) );
}
private:
FILE* m_file;
bool m_bigEndian;
};
class LineReader
{
public:
LineReader(const char* _str)
: m_str(_str)
, m_pos(0)
, m_size( (uint32_t)strlen(_str) )
{
}
std::string getLine()
{
const char* str = &m_str[m_pos];
skipLine();
const char* eol = &m_str[m_pos];
std::string tmp;
tmp.assign(str, eol-str);
return tmp;
}
bool isEof() const
{
return m_str[m_pos] == '\0';
}
private:
void skipLine()
{
const char* str = &m_str[m_pos];
const char* eol = strstr(str, "\n\r");
if (NULL != eol)
{
m_pos += eol-str+2;
return;
}
eol = strstr(str, "\n");
if (NULL != eol)
{
m_pos += eol-str+1;
return;
}
m_pos += (uint32_t)strlen(str);
}
const char* m_str;
uint32_t m_pos;
uint32_t m_size;
};
void printCode(const char* _code)
{
fprintf(stderr, "Code:\n---\n");
LineReader lr(_code);
for (uint32_t line = 1; !lr.isEof(); ++line)
{
fprintf(stderr, "%3d: %s", line, lr.getLine().c_str() );
}
fprintf(stderr, "---\n");
}
bool compileGLSLShader(CommandLine& _cmdLine, const std::string& _code, const char* _outFilePath)
{
const glslopt_shader_type type = (0 == _stricmp(_cmdLine.findOption('\0', "type"), "fragment") ) ? kGlslOptShaderFragment : kGlslOptShaderVertex;
glslopt_ctx* ctx = glslopt_initialize(false);
glslopt_shader* shader = glslopt_optimize(ctx, type, _code.c_str(), 0);
if( !glslopt_get_status(shader) )
{
printCode(_code.c_str() );
fprintf(stderr, "Error: %s\n", glslopt_get_log(shader) );
glslopt_cleanup(ctx);
return false;
}
const char* optimizedShader = glslopt_get_output(shader);
FILE* out = fopen(_outFilePath, "wb");
if (NULL == out)
{
fprintf(stderr, "Unable to open output file '%s'.", _outFilePath);
glslopt_cleanup(ctx);
return false;
}
Stream stream(out);
const char* profile = _cmdLine.findOption('p');
if (NULL == profile)
{
stream.write("#ifdef GL_ES\n");
stream.write("precision highp float;\n");
stream.write("#endif // GL_ES\n\n");
}
else
{
stream.writef("#version %s\n\n", profile);
}
stream.write(optimizedShader, strlen(optimizedShader) );
uint8_t nul = 0;
stream.write(nul);
stream.close();
fclose(out);
glslopt_cleanup(ctx);
return true;
}
bool compileHLSLShader(CommandLine& _cmdLine, const std::string& _code, const char* _outFilePath)
{
#if BX_PLATFORM_WINDOWS
const char* profile = _cmdLine.findOption('p');
if (NULL == profile)
{
printf("Shader profile must be specified.\n");
return false;
}
bool bigEndian = _cmdLine.hasArg('\0', "xbox360");
uint32_t flags = 0;
flags |= _cmdLine.hasArg('\0', "debug") ? D3DXSHADER_DEBUG : 0;
flags |= _cmdLine.hasArg('\0', "avoid-flow-control") ? D3DXSHADER_AVOID_FLOW_CONTROL : 0;
flags |= _cmdLine.hasArg('\0', "no-preshader") ? D3DXSHADER_NO_PRESHADER : 0;
flags |= _cmdLine.hasArg('\0', "partial-precision") ? D3DXSHADER_PARTIALPRECISION : 0;
flags |= _cmdLine.hasArg('\0', "prefer-flow-control") ? D3DXSHADER_PREFER_FLOW_CONTROL : 0;
flags |= _cmdLine.hasArg('\0', "backwards-compatibility") ? D3DXSHADER_ENABLE_BACKWARDS_COMPATIBILITY : 0;
bool werror = _cmdLine.hasArg('\0', "Werror");
uint32_t optimization = 3;
if (_cmdLine.hasArg(optimization, 'O') )
{
optimization = bx::uint32_min(optimization, countof(s_optimizationLevel)-1);
flags |= s_optimizationLevel[optimization];
}
else
{
flags |= D3DXSHADER_SKIPOPTIMIZATION;
}
BX_TRACE("Profile: %s", profile);
BX_TRACE("Flags: 0x%08x", flags);
BX_TRACE("Big Endian: %s", bigEndian?"true":"false");
LPD3DXBUFFER code;
LPD3DXBUFFER errorMsg;
LPD3DXCONSTANTTABLE constantTable;
HRESULT hr = D3DXCompileShader(_code.c_str()
, _code.size()
, NULL
, NULL
, "main"
, profile
, flags
, &code
, &errorMsg
, &constantTable
);
if (FAILED(hr)
|| werror && NULL != errorMsg)
{
printCode(_code.c_str() );
fprintf(stderr, "Error: 0x%08x %s\n", hr, errorMsg->GetBufferPointer() );
return false;
}
D3DXCONSTANTTABLE_DESC desc;
hr = constantTable->GetDesc(&desc);
if (FAILED(hr) )
{
fprintf(stderr, "Error 0x%08x\n", hr);
return false;
}
BX_TRACE("Creator: %s 0x%08x", desc.Creator, desc.Version);
BX_TRACE("Num constants: %d", desc.Constants);
BX_TRACE("# cl ty RxC S By Name");
UniformArray uniforms;
for (uint32_t ii = 0; ii < desc.Constants; ++ii)
{
D3DXHANDLE handle = constantTable->GetConstant(NULL, ii);
D3DXCONSTANT_DESC constDesc;
uint32_t count;
constantTable->GetConstantDesc(handle, &constDesc, &count);
BX_TRACE("%3d %2d %2d [%dx%d] %d %3d %s[%d] c%d (%d)"
, ii
, constDesc.Class
, constDesc.Type
, constDesc.Rows
, constDesc.Columns
, constDesc.StructMembers
, constDesc.Bytes
, constDesc.Name
, constDesc.Elements
, constDesc.RegisterIndex
, constDesc.RegisterCount
);
ConstantType::Enum type = findConstantType(constDesc);
if (ConstantType::Count != type)
{
Uniform un;
un.name = '$' == constDesc.Name[0] ? constDesc.Name+1 : constDesc.Name;
un.type = type;
un.num = constDesc.Elements;
un.regIndex = constDesc.RegisterIndex;
un.regCount = constDesc.RegisterCount;
uniforms.push_back(un);
}
}
FILE* out = fopen(_outFilePath, "wb");
if (NULL == out)
{
fprintf(stderr, "Unable to open output file '%s'.", _outFilePath);
return false;
}
Stream stream(out, bigEndian);
uint16_t count = (uint16_t)uniforms.size();
stream.write(count);
uint32_t fragmentBit = profile[0] == 'p' ? ConstantType::FragmentBit : 0;
for (UniformArray::const_iterator it = uniforms.begin(); it != uniforms.end(); ++it)
{
const Uniform& un = *it;
uint8_t nameSize = (uint8_t)un.name.size();
stream.write(nameSize);
stream.write(un.name.c_str(), nameSize);
stream.write<uint8_t>(un.type|fragmentBit);
stream.write(un.num);
stream.write(un.regIndex);
stream.write(un.regCount);
BX_TRACE("%s, %s, %d, %d, %d"
, un.name.c_str()
, s_constantTypeName[un.type]
, un.num
, un.regIndex
, un.regCount
);
}
uint16_t shaderSize = (uint16_t)code->GetBufferSize();
stream.write(shaderSize);
stream.write(code->GetBufferPointer(), shaderSize);
uint8_t nul = 0;
stream.write(nul);
stream.close();
fclose(out);
if (NULL != code)
{
code->Release();
}
if (NULL != errorMsg)
{
errorMsg->Release();
}
if (NULL != constantTable)
{
constantTable->Release();
}
return true;
#else
fprintf(stderr, "HLSL compiler is not supported on this platform.\n");
return false;
#endif // BX_PLATFORM_WINDOWS
}
struct Preprocessor
{
Preprocessor(const char* _filePath)
: m_tagptr(m_tags)
, m_scratchPos(0)
, m_fgetsPos(0)
{
m_filePath = scratch(_filePath);
m_tagptr->tag = FPPTAG_USERDATA;
m_tagptr->data = this;
m_tagptr++;
m_tagptr->tag = FPPTAG_INPUT;
m_tagptr->data = (void*)fppInput;
m_tagptr++;
m_tagptr->tag = FPPTAG_OUTPUT;
m_tagptr->data = (void*)fppOutput;
m_tagptr++;
m_tagptr->tag = FPPTAG_ERROR;
m_tagptr->data = (void*)fppError;
m_tagptr++;
m_tagptr->tag = FPPTAG_IGNOREVERSION;
m_tagptr->data = (void*)0;
m_tagptr++;
m_tagptr->tag = FPPTAG_LINE;
m_tagptr->data = (void*)0;
m_tagptr++;
m_tagptr->tag = FPPTAG_INPUT_NAME;
m_tagptr->data = m_filePath;
m_tagptr++;
m_default = "#define lowp\n#define mediump\n#define highp\n";
}
void setDefine(const char* _define)
{
m_tagptr->tag = FPPTAG_DEFINE;
m_tagptr->data = scratch(_define);
m_tagptr++;
}
void setDefaultDefine(const char* _name)
{
char temp[1024];
_snprintf(temp, countof(temp)
, "#ifndef %s\n"
"# define %s 0\n"
"#endif // %s\n"
"\n"
, _name
, _name
, _name
);
m_default += temp;
}
bool run()
{
m_fgetsPos = 0;
FILE* file = fopen(m_filePath, "r");
long int size = fsize(file);
char* input = new char[size+1];
size = fread(input, 1, size, file);
input[size] = '\0';
fclose(file);
m_input = m_default;
m_input += input;
fppTag* tagptr = m_tagptr;
tagptr->tag = FPPTAG_END;
tagptr->data = 0;
tagptr++;
int result = fppPreProcess(m_tags);
return 0 == result;
}
char* fgets(char* _buffer, int _size)
{
int ii = 0;
for (char ch = m_input[m_fgetsPos]; m_fgetsPos < m_input.size() && ii < _size-1; ch = m_input[++m_fgetsPos])
{
_buffer[ii++] = ch;
if (ch == '\n' || ii == _size)
{
_buffer[ii] = '\0';
m_fgetsPos++;
return _buffer;
}
}
return NULL;
}
static char* fppInput(char* _buffer, int _size, void* _userData)
{
Preprocessor* thisClass = (Preprocessor*)_userData;
return thisClass->fgets(_buffer, _size);
}
static void fppOutput(int _ch, void* _userData)
{
Preprocessor* thisClass = (Preprocessor*)_userData;
thisClass->m_preprocessed += _ch;
}
static void fppError(void* _userData, char* _format, va_list _vargs)
{
vfprintf(stderr, _format, _vargs);
}
char* scratch(const char* _str)
{
char* result = &m_scratch[m_scratchPos];
strcpy(result, _str);
m_scratchPos += strlen(_str)+1;
return result;
}
fppTag m_tags[MAX_TAGS];
fppTag* m_tagptr;
char* m_filePath;
std::string m_default;
std::string m_input;
std::string m_preprocessed;
char m_scratch[16<<10];
uint32_t m_scratchPos;
uint32_t m_fgetsPos;
};
// OpenGL #version Features Direct3D Features Shader Model
// 2.1 120 vf 9.0 vf 2.0
// 3.0 130
// 3.1 140
// 3.2 150 vgf
// 3.3 330 10.0 vgf 4.0
// 4.0 400 vhdgf
// 4.1 410
// 4.2 420 11.0 vhdgf 5.0
int main(int _argc, const char* _argv[])
{
CommandLine cmdLine(_argc, _argv);
const char* filePath = cmdLine.findOption('f');
if (NULL == filePath)
{
fprintf(stderr, "Shader file name must be specified.\n");
return EXIT_FAILURE;
}
const char* outFilePath = cmdLine.findOption('o');
if (NULL == outFilePath)
{
fprintf(stderr, "Output file name must be specified.\n");
return EXIT_FAILURE;
}
const char* type = cmdLine.findOption('\0', "type");
if (NULL == type)
{
fprintf(stderr, "Must specify shader type.");
return EXIT_FAILURE;
}
const char* platform = cmdLine.findOption('\0', "platform");
if (NULL == platform)
{
fprintf(stderr, "Must specify platform.\n");
return EXIT_FAILURE;
}
bool preprocessOnly = cmdLine.hasArg("preprocess");
Preprocessor preprocessor(filePath);
preprocessor.setDefaultDefine("BX_PLATFORM_ANDROID");
preprocessor.setDefaultDefine("BX_PLATFORM_NACL");
preprocessor.setDefaultDefine("BX_PLATFORM_WINDOWS");
preprocessor.setDefaultDefine("BX_PLATFORM_XBOX360");
preprocessor.setDefaultDefine("BX_PLATFORM_LINUX");
preprocessor.setDefaultDefine("BGFX_SHADER_LANGUAGE_GLSL");
preprocessor.setDefaultDefine("BGFX_SHADER_LANGUAGE_HLSL");
preprocessor.setDefaultDefine("BGFX_SHADER_TYPE_FRAGMENT");
preprocessor.setDefaultDefine("BGFX_SHADER_TYPE_VERTEX");
bool glsl = false;
if (0 == _stricmp(platform, "android") )
{
preprocessor.setDefine("BX_PLATFORM_ANDROID=1");
preprocessor.setDefine("BGFX_SHADER_LANGUAGE_GLSL=1");
glsl = true;
}
else if (0 == _stricmp(platform, "nacl") )
{
preprocessor.setDefine("BX_PLATFORM_NACL=1");
preprocessor.setDefine("BGFX_SHADER_LANGUAGE_GLSL=1");
glsl = true;
}
else if (0 == _stricmp(platform, "linux"))
{
preprocessor.setDefine("BX_PLATFORM_LINUX=1");
preprocessor.setDefine("BGFX_SHADER_LANGUAGE_GLSL=1");
glsl = true;
}
else if (0 == _stricmp(platform, "windows") )
{
preprocessor.setDefine("BX_PLATFORM_WINDOWS=1");
preprocessor.setDefine("BGFX_SHADER_LANGUAGE_HLSL=1");
}
else if (0 == _stricmp(platform, "xbox360") )
{
preprocessor.setDefine("BX_PLATFORM_XBOX360=1");
preprocessor.setDefine("BGFX_SHADER_LANGUAGE_HLSL=1");
}
else
{
fprintf(stderr, "Unknown platform %s?!", platform);
return EXIT_FAILURE;
}
switch (tolower(type[0]) )
{
case 'f':
preprocessor.setDefine("BGFX_SHADER_TYPE_FRAGMENT=1");
break;
case 'v':
preprocessor.setDefine("BGFX_SHADER_TYPE_VERTEX=1");
break;
default:
fprintf(stderr, "Unknown type: %s?!", type);
return EXIT_FAILURE;
}
if (preprocessor.run() )
{
BX_TRACE("Input file: %s", filePath);
BX_TRACE("Output file: %s", outFilePath);
if (preprocessOnly)
{
FILE* out = fopen(outFilePath, "wb");
if (NULL == out)
{
fprintf(stderr, "Unable to open output file '%s'.", outFilePath);
return false;
}
Stream stream(out);
if (glsl)
{
const char* profile = cmdLine.findOption('p');
if (NULL == profile)
{
stream.write("#ifdef GL_ES\n");
stream.write("precision highp float;\n");
stream.write("#endif // GL_ES\n\n");
}
else
{
stream.writef("#version %s\n\n", profile);
}
}
stream.write(preprocessor.m_preprocessed.c_str(), preprocessor.m_preprocessed.size() );
stream.close();
fclose(out);
return EXIT_SUCCESS;
}
if (glsl)
{
if (compileGLSLShader(cmdLine, preprocessor.m_preprocessed, outFilePath) )
{
return EXIT_SUCCESS;
}
}
else
{
if (compileHLSLShader(cmdLine, preprocessor.m_preprocessed, outFilePath) )
{
return EXIT_SUCCESS;
}
}
}
fprintf(stderr, "Failed to build shader.\n");
return EXIT_FAILURE;
}