blob: 43e4e8a8f39b628a877f216cb102fcc7df45dd99 [file] [edit]
/*
* Copyright 2024 Philip Rebohle for Valve Software
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
#define INITGUID
#include <array>
#include <cstring>
#include <iostream>
#include "dxil_library.h"
dxc_library dxc_library::instance;
dxc_library::dxc_library()
{
m_dxcompiler = LoadLibraryA("dxcompiler.dll");
if (!m_dxcompiler)
{
std::wcerr << "Failed to load dxcompiler.dll." << std::endl;
return;
}
m_dxc_create_instance = reinterpret_cast<DxcCreateInstanceProc>(
GetProcAddress(m_dxcompiler, "DxcCreateInstance"));
if (!m_dxc_create_instance)
{
std::wcerr << "DxcCreateInstance not found in dxcompiler.dll." << std::endl;
FreeLibrary(m_dxcompiler);
m_dxcompiler = nullptr;
}
}
dxc_library::~dxc_library()
{
FreeLibrary(m_dxcompiler);
}
bool dxc_library::compile(const std::vector<char> &code, LPCWSTR source_name, LPCWSTR target,
LPCWSTR entry_point, const std::vector<std::wstring> &arguments, std::vector<char> &binary) const
{
if (!m_dxcompiler)
return false;
/* These are not thread-safe, just create new instances each time */
IDxcUtils *utils = nullptr;
HRESULT hr = m_dxc_create_instance(CLSID_DxcUtils, IID_PPV_ARGS(&utils));
if (FAILED(hr))
{
std::wcerr << "Failed to create IDxcUtils instance." << std::endl;
return false;
}
IDxcCompiler3 *compiler = nullptr;
hr = m_dxc_create_instance(CLSID_DxcCompiler, IID_PPV_ARGS(&compiler));
if (FAILED(hr))
{
std::wcerr << "Failed to create IDxcCompiler3 instance." << std::endl;
utils->Release();
return false;
}
IDxcIncludeHandler *include_handler = nullptr;
hr = utils->CreateDefaultIncludeHandler(&include_handler);
if (FAILED(hr))
{
std::wcerr << "Failed to create include handler." << std::endl;
utils->Release();
compiler->Release();
return false;
}
/* Set up parameters that minimize the generated binary size */
IDxcCompilerArgs *args = nullptr;
std::vector<LPCWSTR> argument_list = {
L"-Qstrip_debug",
L"-Qstrip_reflect",
};
for (const auto &arg : arguments)
argument_list.push_back(arg.c_str());
hr = utils->BuildArguments(source_name, entry_point, target,
argument_list.data(), argument_list.size(), nullptr, 0, &args);
if (FAILED(hr))
{
std::wcerr << "Failed to build compiler arguments." << std::endl;
include_handler->Release();
utils->Release();
compiler->Release();
return false;
}
IDxcResult *result = nullptr;
/* Assume that shader code is some sort of single-byte encoding */
DxcBuffer source_buffer = { };
source_buffer.Ptr = code.data();
source_buffer.Size = code.size();
source_buffer.Encoding = DXC_CP_UTF8;
hr = compiler->Compile(&source_buffer,
args->GetArguments(), args->GetCount(),
include_handler, IID_PPV_ARGS(&result));
/* Compile will still return S_OK if compilation failed, we need to ask
* the result object for the status of the operation, and for some bizarre
* reason that call returns a HRESULT itself, so we have three layers of
* error checking here. Just make sure we end up with an error status at
* the end if anything fails here. */
if (SUCCEEDED(hr))
{
HRESULT status = S_OK;
hr = result->GetStatus(&status);
if (SUCCEEDED(hr))
hr = status;
}
bool status = SUCCEEDED(hr);
if (status)
{
/* Compilation successful, just retrieve the binary blob and return it. */
IDxcBlob *binary_blob = nullptr;
if (result && result->HasOutput(DXC_OUT_OBJECT))
{
hr = result->GetOutput(DXC_OUT_OBJECT, IID_PPV_ARGS(&binary_blob), nullptr);
if (SUCCEEDED(hr))
{
binary.resize(binary_blob->GetBufferSize());
std::memcpy(binary.data(), binary_blob->GetBufferPointer(), binary.size());
}
}
if (binary_blob)
binary_blob->Release();
else
std::wcerr << "Failed to retrieve shader binary." << std::endl;
}
else if (result)
{
/* Try to print, retrieve and convert error messages to a format that we
* can print to the console. More nested error checking madness here. */
std::wcerr << "Failed to compile DXIL shader '" << source_name << "':" << std::endl;
if (result->HasOutput(DXC_OUT_ERRORS))
{
IDxcBlobEncoding *error_blob = nullptr;
IDxcBlobWide *error_blob_wide = nullptr;
hr = result->GetOutput(DXC_OUT_ERRORS, IID_PPV_ARGS(&error_blob), nullptr);
if (SUCCEEDED(hr) && SUCCEEDED(utils->GetBlobAsWide(error_blob, &error_blob_wide)))
{
std::wcerr << error_blob_wide->GetStringPointer() << std::endl;
error_blob_wide->Release();
}
else
{
std::wcerr << "Failed to get errors." << std::endl;
}
if (error_blob)
error_blob->Release();
}
}
if (result)
result->Release();
args->Release();
include_handler->Release();
compiler->Release();
utils->Release();
return status;
}