#include "ShaderD3D9.h" #include "GeometryD3D9.h" #include "DeviceD3D9.h" #include #include #include #include #pragma comment(lib, "d3dx9.lib") LOGGROUP(Graphics) namespace RBX { namespace Graphics { static void extractUniforms(ID3DXConstantTable* ctab, const std::string& prefix, D3DXHANDLE container, unsigned int size, std::vector& result, unsigned int& samplerMask) { for (unsigned int i = 0; i < size; ++i) { D3DXHANDLE constant = ctab->GetConstant(container, i); D3DXCONSTANT_DESC cdesc; unsigned int cdescCount = 1; ctab->GetConstantDesc(constant, &cdesc, &cdescCount); // Trim leading $ (for function-local uniforms) const char* name = cdesc.Name + (cdesc.Name[0] == '$'); if (cdesc.Class == D3DXPC_STRUCT) { extractUniforms(ctab, prefix + name + ".", constant, cdesc.StructMembers, result, samplerMask); } else if (cdesc.RegisterSet == D3DXRS_SAMPLER) { samplerMask |= 1 << cdesc.RegisterIndex; } else if (cdesc.RegisterCount > 0) { UniformD3D9 u; u.name = prefix + name; u.registerSet = (cdesc.RegisterSet == D3DXRS_BOOL) ? UniformD3D9::Register_Bool : (cdesc.RegisterSet == D3DXRS_INT4) ? UniformD3D9::Register_Int : UniformD3D9::Register_Float; u.registerIndex = cdesc.RegisterIndex; u.registerCount = cdesc.RegisterCount; result.push_back(u); } } } static std::vector extractUniforms(ID3DXConstantTable* ctab, unsigned int* outSamplerMask) { std::vector result; unsigned int samplerMask = 0; D3DXCONSTANTTABLE_DESC desc; ctab->GetDesc(&desc); extractUniforms(ctab, "", NULL, desc.Constants, result, samplerMask); if (outSamplerMask) *outSamplerMask = samplerMask; return result; } static std::vector extractUniforms(const DWORD* bytecodePointer, unsigned int* outSamplerMask) { ID3DXConstantTable* ctab; HRESULT hr = D3DXGetShaderConstantTable(bytecodePointer, &ctab); if (SUCCEEDED(hr)) { if (!ctab) throw RBX::runtime_error("Error parsing bytecode: no constant table present"); std::vector result = extractUniforms(ctab, outSamplerMask); ctab->Release(); return result; } else throw RBX::runtime_error("Error parsing bytecode: %x", hr); } static void disableUniform(UniformD3D9& u) { u.name.clear(); } struct UniformDisabledPredicate { bool operator()(const UniformD3D9& u) const { return u.name.empty(); } }; static void validateUniformsAndDisableGlobal(std::vector& uniforms, size_t globalDataSize, const std::map& globalConstants) { size_t globalDataRegisterCount = globalDataSize / 16; for (size_t i = 0; i < uniforms.size(); ++i) { UniformD3D9& u = uniforms[i]; if (u.registerSet != UniformD3D9::Register_Float) throw RBX::runtime_error("Uniform %s has unsupported register set %d", u.name.c_str(), u.registerSet); if (u.registerIndex + u.registerCount <= globalDataRegisterCount) { std::map::const_iterator git = globalConstants.find(u.name); if (git == globalConstants.end()) throw RBX::runtime_error("Uniform %s register range [%d..%d) overlaps with global register range [0..%d)", u.name.c_str(), u.registerIndex, u.registerIndex + u.registerCount, globalDataRegisterCount); unsigned int expectedRegisterIndex = git->second.offset / 16; unsigned int expectedRegisterCount = git->second.size / 16; if (u.registerIndex != expectedRegisterIndex || u.registerCount > expectedRegisterCount) throw RBX::runtime_error("Uniform %s register range [%d..%d) is not a prefix of expected register range [%d..%d)", u.name.c_str(), u.registerIndex, u.registerIndex + u.registerCount, expectedRegisterIndex, expectedRegisterIndex + expectedRegisterCount); disableUniform(u); } } } static void removeDisabledUniforms(std::vector& uniforms) { uniforms.erase(std::remove_if(uniforms.begin(), uniforms.end(), UniformDisabledPredicate()), uniforms.end()); } static int findUniform(const std::vector& uniforms, const char* name) { for (size_t i = 0; i < uniforms.size(); ++i) if (uniforms[i].name == name) return i; return -1; } static void reloadUniforms(std::vector& uniforms, const std::vector& newUniforms) { for (size_t i = 0; i < uniforms.size(); ++i) { int newIndex = findUniform(newUniforms, uniforms[i].name.c_str()); if (newIndex >= 0) { uniforms[i] = newUniforms[newIndex]; } else { uniforms[i].registerIndex = 0; uniforms[i].registerCount = 0; } } } static IDirect3DVertexShader9* createVertexShader(Device* device, const std::vector& bytecode, std::vector& uniforms, int& registerWorldMatrix, int& registerWorldMatrixArray, unsigned int& maxWorldTransforms) { RBXASSERT(bytecode.size() > 0 && bytecode.size() % sizeof(DWORD) == 0); const DWORD* bytecodePointer = reinterpret_cast(&bytecode[0]); uniforms = extractUniforms(bytecodePointer, NULL); validateUniformsAndDisableGlobal(uniforms, static_cast(device)->getGlobalDataSize(), static_cast(device)->getGlobalConstants()); int uniformWorldMatrix = findUniform(uniforms, "WorldMatrix"); if (uniformWorldMatrix >= 0) { UniformD3D9& u = uniforms[uniformWorldMatrix]; registerWorldMatrix = u.registerIndex; maxWorldTransforms = 1; disableUniform(u); } int uniformWorldMatrixArray = findUniform(uniforms, "WorldMatrixArray"); if (uniformWorldMatrixArray >= 0) { UniformD3D9& u = uniforms[uniformWorldMatrixArray]; RBXASSERT(u.registerCount % 3 == 0); registerWorldMatrixArray = u.registerIndex; maxWorldTransforms = u.registerCount / 3; disableUniform(u); } removeDisabledUniforms(uniforms); IDirect3DDevice9* device9 = static_cast(device)->getDevice9(); IDirect3DVertexShader9* object = NULL; HRESULT hr = device9->CreateVertexShader(bytecodePointer, &object); if (FAILED(hr)) throw RBX::runtime_error("Error creating vertex shader: %x", hr); return object; } static IDirect3DPixelShader9* createPixelShader(Device* device, const std::vector& bytecode, std::vector& uniforms, unsigned int& samplerMask) { RBXASSERT(bytecode.size() > 0 && bytecode.size() % sizeof(DWORD) == 0); const DWORD* bytecodePointer = reinterpret_cast(&bytecode[0]); uniforms = extractUniforms(bytecodePointer, &samplerMask); validateUniformsAndDisableGlobal(uniforms, static_cast(device)->getGlobalDataSize(), static_cast(device)->getGlobalConstants()); removeDisabledUniforms(uniforms); IDirect3DDevice9* device9 = static_cast(device)->getDevice9(); IDirect3DPixelShader9* object = NULL; HRESULT hr = device9->CreatePixelShader(bytecodePointer, &object); if (FAILED(hr)) throw RBX::runtime_error("Error creating fragment shader: %x", hr); return object; } VertexShaderD3D9::VertexShaderD3D9(Device* device, const std::vector& bytecode) : VertexShader(device) , object(NULL) , registerWorldMatrix(-1) , registerWorldMatrixArray(-1) , maxWorldTransforms(0) { object = createVertexShader(device, bytecode, uniforms, registerWorldMatrix, registerWorldMatrixArray, maxWorldTransforms); } VertexShaderD3D9::~VertexShaderD3D9() { object->Release(); } void VertexShaderD3D9::reloadBytecode(const std::vector& bytecode) { std::vector newUniforms; int newRegisterWorldMatrix = -1; int newRegisterWorldMatrixArray = -1; unsigned int newMaxWorldTransforms = 0; IDirect3DVertexShader9* newObject = createVertexShader(device, bytecode, newUniforms, newRegisterWorldMatrix, newRegisterWorldMatrixArray, newMaxWorldTransforms); if (object) object->Release(); object = newObject; registerWorldMatrix = newRegisterWorldMatrix; registerWorldMatrixArray = newRegisterWorldMatrixArray; maxWorldTransforms = newMaxWorldTransforms; reloadUniforms(uniforms, newUniforms); } FragmentShaderD3D9::FragmentShaderD3D9(Device* device, const std::vector& bytecode) : FragmentShader(device) , object(NULL) , samplerMask(0) { object = createPixelShader(device, bytecode, uniforms, samplerMask); } FragmentShaderD3D9::~FragmentShaderD3D9() { object->Release(); } void FragmentShaderD3D9::reloadBytecode(const std::vector& bytecode) { std::vector newUniforms; unsigned int newSamplerMask = 0; IDirect3DPixelShader9* newObject = createPixelShader(device, bytecode, newUniforms, newSamplerMask); if (object) object->Release(); object = newObject; samplerMask = newSamplerMask; reloadUniforms(uniforms, newUniforms); } ShaderProgramD3D9::ShaderProgramD3D9(Device* device, const shared_ptr& vertexShader, const shared_ptr& fragmentShader) : ShaderProgram(device, vertexShader, fragmentShader) { } ShaderProgramD3D9::~ShaderProgramD3D9() { static_cast(device)->getImmediateContextD3D9()->invalidateCachedProgram(); } void ShaderProgramD3D9::bind() { IDirect3DDevice9* device9 = static_cast(device)->getDevice9(); device9->SetVertexShader(static_cast(vertexShader.get())->getObject()); device9->SetPixelShader(static_cast(fragmentShader.get())->getObject()); } void ShaderProgramD3D9::setWorldTransforms4x3(const float* data, size_t matrixCount) { if (matrixCount == 0) return; IDirect3DDevice9* device9 = static_cast(device)->getDevice9(); VertexShaderD3D9* vs9 = static_cast(vertexShader.get()); if (vs9->getRegisterWorldMatrix() >= 0) { static const float lastColumn[] = {0, 0, 0, 1}; float matrix[16]; memcpy(matrix, data, 12 * sizeof(float)); memcpy(matrix + 12, lastColumn, 4 * sizeof(float)); device9->SetVertexShaderConstantF(vs9->getRegisterWorldMatrix(), matrix, 4); } if (vs9->getRegisterWorldMatrixArray() >= 0) { RBXASSERT(matrixCount <= vs9->getMaxWorldTransforms()); device9->SetVertexShaderConstantF(vs9->getRegisterWorldMatrixArray(), data, matrixCount * 3); } } void ShaderProgramD3D9::setConstant(int handle, const float* data, size_t vectorCount) { if (handle < 0) return; IDirect3DDevice9* device9 = static_cast(device)->getDevice9(); int vs = (handle & 0xffff) - 1; int fs = (handle >> 16) - 1; if (vs >= 0) { const std::vector& uniforms = static_cast(vertexShader.get())->getUniforms(); RBXASSERT(static_cast(vs) < uniforms.size()); const UniformD3D9& u = uniforms[vs]; if (u.registerCount) { RBXASSERT(vectorCount >= u.registerCount); device9->SetVertexShaderConstantF(u.registerIndex, data, u.registerCount); } } if (fs >= 0) { const std::vector& uniforms = static_cast(fragmentShader.get())->getUniforms(); RBXASSERT(static_cast(fs) < uniforms.size()); const UniformD3D9& u = uniforms[fs]; if (u.registerCount) { RBXASSERT(vectorCount >= u.registerCount); device9->SetPixelShaderConstantF(u.registerIndex, data, u.registerCount); } } } int ShaderProgramD3D9::getConstantHandle(const char* name) const { int vs = findUniform(static_cast(vertexShader.get())->getUniforms(), name); int fs = findUniform(static_cast(fragmentShader.get())->getUniforms(), name); RBXASSERT(vs >= -1 && fs >= -1); if (vs < 0 && fs < 0) return -1; else return (vs + 1) | ((fs + 1) << 16); } unsigned int ShaderProgramD3D9::getMaxWorldTransforms() const { return static_cast(vertexShader.get())->getMaxWorldTransforms(); } unsigned int ShaderProgramD3D9::getSamplerMask() const { return static_cast(fragmentShader.get())->getSamplerMask(); } struct IncludeCallback: ID3DXInclude { boost::function fileCallback; std::map paths; IncludeCallback(boost::function fileCallback) : fileCallback(fileCallback) { } virtual HRESULT STDMETHODCALLTYPE Open(D3DXINCLUDE_TYPE IncludeType, LPCSTR pFileName, LPCVOID pParentData, LPCVOID *ppData, UINT *pBytes) { std::string path; if (pParentData) { RBXASSERT(paths.count(pParentData)); path = paths[pParentData]; std::string::size_type slash = path.find_last_of("\\/"); path.erase(path.begin() + (slash == std::string::npos ? 0 : slash + 1), path.end()); } path += pFileName; try { std::string source = fileCallback(path); char* result = new char[source.length()]; memcpy(result, source.c_str(), source.length()); paths[result] = path; *ppData = result; *pBytes = source.length(); return S_OK; } catch (...) { return D3DXFERR_FILENOTFOUND; } } virtual HRESULT STDMETHODCALLTYPE Close(LPCVOID pData) { RBXASSERT(paths.count(pData)); paths.erase(pData); delete[] static_cast(pData); return S_OK; } }; typedef HRESULT (WINAPI *TypeD3DXCompileShader)(LPCSTR, UINT, CONST D3DXMACRO*, LPD3DXINCLUDE, LPCSTR, LPCSTR, DWORD, LPD3DXBUFFER*, LPD3DXBUFFER*, LPD3DXCONSTANTTABLE*); typedef HRESULT (WINAPI *TypeD3DXPreprocessShader)(LPCSTR, UINT, CONST D3DXMACRO*, LPD3DXINCLUDE, LPD3DXBUFFER*, LPD3DXBUFFER*); static HMODULE loadShaderCompilerDLL() { static HMODULE d3dx; if (!d3dx) { for (int version = 43; version >= 24; --version) { d3dx = LoadLibraryA(format("d3dx9_%d.dll", version).c_str()); if (d3dx) { LoadLibraryA(format("D3DCompiler_%d.dll", version).c_str()); break; } } } return d3dx; } static TypeD3DXCompileShader loadShaderCompiler() { HMODULE d3dx = loadShaderCompilerDLL(); return d3dx ? (TypeD3DXCompileShader)GetProcAddress(d3dx, "D3DXCompileShader") : NULL; } static TypeD3DXPreprocessShader loadShaderPreprocessor() { HMODULE d3dx = loadShaderCompilerDLL(); return d3dx ? (TypeD3DXPreprocessShader)GetProcAddress(d3dx, "D3DXPreprocessShader") : NULL; } template static std::vector consumeData(HRESULT hr, ID3DXBuffer* buffer, ID3DXBuffer* messages) { if (SUCCEEDED(hr)) { if (messages) { std::string log(static_cast(messages->GetBufferPointer()), messages->GetBufferSize()); messages->Release(); FASTLOG(FLog::Graphics, "Shader compilation resulted in warnings:"); ShaderProgram::dumpToFLog(log.c_str(), FLog::Graphics); } RBXASSERT(buffer->GetBufferSize() % sizeof(T) == 0); std::vector result(static_cast(buffer->GetBufferPointer()), static_cast(buffer->GetBufferPointer()) + buffer->GetBufferSize() / sizeof(T)); buffer->Release(); return result; } else if (messages) { std::string log(static_cast(messages->GetBufferPointer()), messages->GetBufferSize()); messages->Release(); throw std::runtime_error(log.c_str()); } else throw RBX::runtime_error("Unknown error %x", hr); } std::string ShaderProgramD3D9::createShaderSource(const std::string& path, const std::string& defines, boost::function fileCallback) { TypeD3DXPreprocessShader preprocessor = loadShaderPreprocessor(); if (!preprocessor) throw RBX::runtime_error("Unable to load shader preprocessor"); // split define string into strings std::vector defineStrings; std::istringstream defineStream(defines); std::string defineTemp; while (defineStream >> defineTemp) defineStrings.push_back(defineTemp); // create d3dx defines std::vector macros; for (size_t i = 0; i < defineStrings.size(); ++i) { std::string& def = defineStrings[i]; std::string::size_type pos = def.find('='); if (pos == std::string::npos) { D3DXMACRO macro = {def.c_str(), "1"}; macros.push_back(macro); } else { // split string into name and value def[pos] = 0; D3DXMACRO macro = {def.c_str(), def.c_str() + pos + 1}; macros.push_back(macro); } } D3DXMACRO macroEnd = {}; macros.push_back(macroEnd); // let preprocessor know about the original filename std::string source = "#include \"" + path + "\"\n"; // preprocess shader IncludeCallback callback(fileCallback); ID3DXBuffer* text = NULL; ID3DXBuffer* messages = NULL; HRESULT hr = preprocessor(source.c_str(), source.length(), ¯os[0], &callback, &text, &messages); std::vector resultBuffer = consumeData(hr, text, messages); std::string result = &resultBuffer[0]; // preprocessor output includes a #line 1 "\memory"; remove it! if (result.size() > 10 && result.compare(0, 9, "#line 1 \"") == 0 && result[10] == ':') { std::string::size_type pos = result.find_first_of('\n'); assert(pos != std::string::npos); result.erase(result.begin(), result.begin() + pos); } return result; } std::vector ShaderProgramD3D9::createShaderBytecode(const std::string& source, const std::string& target, const std::string& entrypoint) { TypeD3DXCompileShader compiler = loadShaderCompiler(); if (!compiler) throw RBX::runtime_error("Unable to load shader compiler"); unsigned int flags = D3DXSHADER_PACKMATRIX_ROWMAJOR; ID3DXBuffer* bytecode = NULL; ID3DXBuffer* messages = NULL; HRESULT hr = compiler(source.c_str(), source.length(), NULL, NULL, entrypoint.c_str(), target.c_str(), flags, &bytecode, &messages, NULL); return consumeData(hr, bytecode, messages); } ShaderProgramFFPD3D9::ShaderProgramFFPD3D9(Device* device) : ShaderProgram(device, shared_ptr(), shared_ptr()) { } ShaderProgramFFPD3D9::~ShaderProgramFFPD3D9() { } int ShaderProgramFFPD3D9::getConstantHandle(const char* name) const { if (strcmp(name, "Color") == 0) return 0; return -1; } unsigned int ShaderProgramFFPD3D9::getMaxWorldTransforms() const { return 1; } unsigned int ShaderProgramFFPD3D9::getSamplerMask() const { return 0; } } }