blob: f93bb00ba41dabee13c45a76dbd9d70e72ef7132 [file] [log] [blame]
/*-------------------------------------------------------------------------
* Vulkan Conformance Tests
* ------------------------
*
* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*//*!
* \file
* \brief Utility for pre-compiling source programs to SPIR-V
*//*--------------------------------------------------------------------*/
#include "tcuDefs.hpp"
#include "tcuCommandLine.hpp"
#include "tcuPlatform.hpp"
#include "tcuResource.hpp"
#include "tcuTestLog.hpp"
#include "tcuTestHierarchyIterator.hpp"
#include "deUniquePtr.hpp"
#include "vkPrograms.hpp"
#include "vkBinaryRegistry.hpp"
#include "vktTestCase.hpp"
#include "vktTestPackage.hpp"
#include "deUniquePtr.hpp"
#include "deCommandLine.hpp"
#include "deSharedPtr.hpp"
#include "deThread.hpp"
#include "deThreadSafeRingBuffer.hpp"
#include "dePoolArray.hpp"
#include <iostream>
using std::vector;
using std::string;
using de::UniquePtr;
using de::MovePtr;
using de::SharedPtr;
namespace vkt
{
namespace // anonymous
{
typedef de::SharedPtr<glu::ProgramSources> ProgramSourcesSp;
typedef de::SharedPtr<vk::SpirVAsmSource> SpirVAsmSourceSp;
typedef de::SharedPtr<vk::ProgramBinary> ProgramBinarySp;
class Task
{
public:
virtual void execute (void) = 0;
};
typedef de::ThreadSafeRingBuffer<Task*> TaskQueue;
class TaskExecutorThread : public de::Thread
{
public:
TaskExecutorThread (TaskQueue& tasks)
: m_tasks(tasks)
{
start();
}
void run (void)
{
for (;;)
{
Task* const task = m_tasks.popBack();
if (task)
task->execute();
else
break; // End of tasks - time to terminate
}
}
private:
TaskQueue& m_tasks;
};
class TaskExecutor
{
public:
TaskExecutor (deUint32 numThreads);
~TaskExecutor (void);
void submit (Task* task);
void waitForComplete (void);
private:
typedef de::SharedPtr<TaskExecutorThread> ExecThreadSp;
std::vector<ExecThreadSp> m_threads;
TaskQueue m_tasks;
};
TaskExecutor::TaskExecutor (deUint32 numThreads)
: m_threads (numThreads)
, m_tasks (m_threads.size() * 1024u)
{
for (size_t ndx = 0; ndx < m_threads.size(); ++ndx)
m_threads[ndx] = ExecThreadSp(new TaskExecutorThread(m_tasks));
}
TaskExecutor::~TaskExecutor (void)
{
for (size_t ndx = 0; ndx < m_threads.size(); ++ndx)
m_tasks.pushFront(DE_NULL);
for (size_t ndx = 0; ndx < m_threads.size(); ++ndx)
m_threads[ndx]->join();
}
void TaskExecutor::submit (Task* task)
{
DE_ASSERT(task);
m_tasks.pushFront(task);
}
class SyncTask : public Task
{
public:
SyncTask (de::Semaphore* enterBarrier, de::Semaphore* inBarrier, de::Semaphore* leaveBarrier)
: m_enterBarrier (enterBarrier)
, m_inBarrier (inBarrier)
, m_leaveBarrier (leaveBarrier)
{}
SyncTask (void)
: m_enterBarrier (DE_NULL)
, m_inBarrier (DE_NULL)
, m_leaveBarrier (DE_NULL)
{}
void execute (void)
{
m_enterBarrier->increment();
m_inBarrier->decrement();
m_leaveBarrier->increment();
}
private:
de::Semaphore* m_enterBarrier;
de::Semaphore* m_inBarrier;
de::Semaphore* m_leaveBarrier;
};
void TaskExecutor::waitForComplete (void)
{
de::Semaphore enterBarrier (0);
de::Semaphore inBarrier (0);
de::Semaphore leaveBarrier (0);
std::vector<SyncTask> syncTasks (m_threads.size());
for (size_t ndx = 0; ndx < m_threads.size(); ++ndx)
{
syncTasks[ndx] = SyncTask(&enterBarrier, &inBarrier, &leaveBarrier);
submit(&syncTasks[ndx]);
}
for (size_t ndx = 0; ndx < m_threads.size(); ++ndx)
enterBarrier.decrement();
for (size_t ndx = 0; ndx < m_threads.size(); ++ndx)
inBarrier.increment();
for (size_t ndx = 0; ndx < m_threads.size(); ++ndx)
leaveBarrier.decrement();
}
struct Program
{
enum Status
{
STATUS_NOT_COMPLETED = 0,
STATUS_FAILED,
STATUS_PASSED,
STATUS_LAST
};
vk::ProgramIdentifier id;
Status buildStatus;
std::string buildLog;
ProgramBinarySp binary;
Status validationStatus;
std::string validationLog;
vk::SpirvValidatorOptions validatorOptions;
explicit Program (const vk::ProgramIdentifier& id_, const vk::SpirvValidatorOptions& valOptions_)
: id (id_)
, buildStatus (STATUS_NOT_COMPLETED)
, validationStatus (STATUS_NOT_COMPLETED)
, validatorOptions (valOptions_)
{}
Program (void)
: id ("", "")
, buildStatus (STATUS_NOT_COMPLETED)
, validationStatus (STATUS_NOT_COMPLETED)
, validatorOptions()
{}
};
void writeBuildLogs (const glu::ShaderProgramInfo& buildInfo, std::ostream& dst)
{
for (size_t shaderNdx = 0; shaderNdx < buildInfo.shaders.size(); shaderNdx++)
{
const glu::ShaderInfo& shaderInfo = buildInfo.shaders[shaderNdx];
const char* const shaderName = getShaderTypeName(shaderInfo.type);
dst << shaderName << " source:\n"
<< "---\n"
<< shaderInfo.source << "\n"
<< "---\n"
<< shaderName << " compile log:\n"
<< "---\n"
<< shaderInfo.infoLog << "\n"
<< "---\n";
}
dst << "link log:\n"
<< "---\n"
<< buildInfo.program.infoLog << "\n"
<< "---\n";
}
template <typename Source>
class BuildHighLevelShaderTask : public Task
{
public:
BuildHighLevelShaderTask (const Source& source, Program* program)
: m_source (source)
, m_program (program)
, m_commandLine (0)
{}
BuildHighLevelShaderTask (void) : m_program(DE_NULL) {}
void setCommandline (const tcu::CommandLine &commandLine)
{
m_commandLine = &commandLine;
}
void execute (void)
{
glu::ShaderProgramInfo buildInfo;
try
{
DE_ASSERT(m_source.buildOptions.targetVersion < vk::SPIRV_VERSION_LAST);
DE_ASSERT(m_commandLine != DE_NULL);
m_program->binary = ProgramBinarySp(vk::buildProgram(m_source, &buildInfo, *m_commandLine));
m_program->buildStatus = Program::STATUS_PASSED;
m_program->validatorOptions = m_source.buildOptions.getSpirvValidatorOptions();
}
catch (const tcu::Exception&)
{
std::ostringstream log;
writeBuildLogs(buildInfo, log);
m_program->buildStatus = Program::STATUS_FAILED;
m_program->buildLog = log.str();
}
}
private:
Source m_source;
Program* m_program;
const tcu::CommandLine* m_commandLine;
};
void writeBuildLogs (const vk::SpirVProgramInfo& buildInfo, std::ostream& dst)
{
dst << "source:\n"
<< "---\n"
<< buildInfo.source << "\n"
<< "---\n"
<< buildInfo.infoLog << "\n"
<< "---\n";
}
class BuildSpirVAsmTask : public Task
{
public:
BuildSpirVAsmTask (const vk::SpirVAsmSource& source, Program* program)
: m_source (source)
, m_program (program)
, m_commandLine (0)
{}
BuildSpirVAsmTask (void) : m_program(DE_NULL) {}
void setCommandline (const tcu::CommandLine &commandLine)
{
m_commandLine = &commandLine;
}
void execute (void)
{
vk::SpirVProgramInfo buildInfo;
try
{
DE_ASSERT(m_source.buildOptions.targetVersion < vk::SPIRV_VERSION_LAST);
DE_ASSERT(m_commandLine != DE_NULL);
m_program->binary = ProgramBinarySp(vk::assembleProgram(m_source, &buildInfo, *m_commandLine));
m_program->buildStatus = Program::STATUS_PASSED;
}
catch (const tcu::Exception&)
{
std::ostringstream log;
writeBuildLogs(buildInfo, log);
m_program->buildStatus = Program::STATUS_FAILED;
m_program->buildLog = log.str();
}
}
private:
vk::SpirVAsmSource m_source;
Program* m_program;
const tcu::CommandLine* m_commandLine;
};
class ValidateBinaryTask : public Task
{
public:
ValidateBinaryTask (Program* program)
: m_program(program)
{}
void execute (void)
{
DE_ASSERT(m_program->buildStatus == Program::STATUS_PASSED);
DE_ASSERT(m_program->binary->getFormat() == vk::PROGRAM_FORMAT_SPIRV);
std::ostringstream validationLogStream;
if (vk::validateProgram(*m_program->binary, &validationLogStream, m_program->validatorOptions))
m_program->validationStatus = Program::STATUS_PASSED;
else
m_program->validationStatus = Program::STATUS_FAILED;
m_program->validationLog = validationLogStream.str();
}
private:
Program* m_program;
};
tcu::TestPackageRoot* createRoot (tcu::TestContext& testCtx)
{
vector<tcu::TestNode*> children;
children.push_back(new TestPackage(testCtx));
return new tcu::TestPackageRoot(testCtx, children);
}
} // anonymous
struct BuildStats
{
int numSucceeded;
int numFailed;
int notSupported;
BuildStats (void)
: numSucceeded (0)
, numFailed (0)
, notSupported (0)
{
}
};
BuildStats buildPrograms (tcu::TestContext& testCtx,
const std::string& dstPath,
const bool validateBinaries,
const deUint32 usedVulkanVersion,
const vk::SpirvVersion baselineSpirvVersion,
const vk::SpirvVersion maxSpirvVersion,
const bool allowSpirV14)
{
const deUint32 numThreads = deGetNumAvailableLogicalCores();
TaskExecutor executor (numThreads);
// de::PoolArray<> is faster to build than std::vector
de::MemPool programPool;
de::PoolArray<Program> programs (&programPool);
int notSupported = 0;
{
de::MemPool tmpPool;
de::PoolArray<BuildHighLevelShaderTask<vk::GlslSource> > buildGlslTasks (&tmpPool);
de::PoolArray<BuildHighLevelShaderTask<vk::HlslSource> > buildHlslTasks (&tmpPool);
de::PoolArray<BuildSpirVAsmTask> buildSpirvAsmTasks (&tmpPool);
// Collect build tasks
{
const UniquePtr<tcu::TestPackageRoot> root (createRoot(testCtx));
tcu::DefaultHierarchyInflater inflater (testCtx);
de::MovePtr<tcu::CaseListFilter> caseListFilter (testCtx.getCommandLine().createCaseListFilter(testCtx.getArchive()));
tcu::TestHierarchyIterator iterator (*root, inflater, *caseListFilter);
while (iterator.getState() != tcu::TestHierarchyIterator::STATE_FINISHED)
{
if (iterator.getState() == tcu::TestHierarchyIterator::STATE_ENTER_NODE &&
tcu::isTestNodeTypeExecutable(iterator.getNode()->getNodeType()))
{
TestCase* const testCase = dynamic_cast<TestCase*>(iterator.getNode());
const string casePath = iterator.getNodePath();
vk::ShaderBuildOptions defaultGlslBuildOptions (usedVulkanVersion, baselineSpirvVersion, 0u);
vk::ShaderBuildOptions defaultHlslBuildOptions (usedVulkanVersion, baselineSpirvVersion, 0u);
vk::SpirVAsmBuildOptions defaultSpirvAsmBuildOptions (usedVulkanVersion, baselineSpirvVersion);
vk::SourceCollections sourcePrograms (usedVulkanVersion, defaultGlslBuildOptions, defaultHlslBuildOptions, defaultSpirvAsmBuildOptions);
try
{
testCase->delayedInit();
testCase->initPrograms(sourcePrograms);
}
catch (const tcu::NotSupportedError& )
{
notSupported++;
iterator.next();
continue;
}
for (vk::GlslSourceCollection::Iterator progIter = sourcePrograms.glslSources.begin();
progIter != sourcePrograms.glslSources.end();
++progIter)
{
// Source program requires higher SPIR-V version than available: skip it to avoid fail
// Unless this is SPIR-V 1.4 and is explicitly allowed.
if (progIter.getProgram().buildOptions.targetVersion > maxSpirvVersion && !(allowSpirV14 && progIter.getProgram().buildOptions.supports_VK_KHR_spirv_1_4 && progIter.getProgram().buildOptions.targetVersion == vk::SPIRV_VERSION_1_4))
continue;
programs.pushBack(Program(vk::ProgramIdentifier(casePath, progIter.getName()), progIter.getProgram().buildOptions.getSpirvValidatorOptions()));
buildGlslTasks.pushBack(BuildHighLevelShaderTask<vk::GlslSource>(progIter.getProgram(), &programs.back()));
buildGlslTasks.back().setCommandline(testCtx.getCommandLine());
executor.submit(&buildGlslTasks.back());
}
for (vk::HlslSourceCollection::Iterator progIter = sourcePrograms.hlslSources.begin();
progIter != sourcePrograms.hlslSources.end();
++progIter)
{
// Source program requires higher SPIR-V version than available: skip it to avoid fail
// Unless this is SPIR-V 1.4 and is explicitly allowed.
if (progIter.getProgram().buildOptions.targetVersion > maxSpirvVersion && !(allowSpirV14 && progIter.getProgram().buildOptions.supports_VK_KHR_spirv_1_4 && progIter.getProgram().buildOptions.targetVersion == vk::SPIRV_VERSION_1_4))
continue;
programs.pushBack(Program(vk::ProgramIdentifier(casePath, progIter.getName()), progIter.getProgram().buildOptions.getSpirvValidatorOptions()));
buildHlslTasks.pushBack(BuildHighLevelShaderTask<vk::HlslSource>(progIter.getProgram(), &programs.back()));
buildHlslTasks.back().setCommandline(testCtx.getCommandLine());
executor.submit(&buildHlslTasks.back());
}
for (vk::SpirVAsmCollection::Iterator progIter = sourcePrograms.spirvAsmSources.begin();
progIter != sourcePrograms.spirvAsmSources.end();
++progIter)
{
// Source program requires higher SPIR-V version than available: skip it to avoid fail
// Unless this is SPIR-V 1.4 and is explicitly allowed.
if (progIter.getProgram().buildOptions.targetVersion > maxSpirvVersion && !(allowSpirV14 && progIter.getProgram().buildOptions.supports_VK_KHR_spirv_1_4 && progIter.getProgram().buildOptions.targetVersion == vk::SPIRV_VERSION_1_4))
continue;
programs.pushBack(Program(vk::ProgramIdentifier(casePath, progIter.getName()), progIter.getProgram().buildOptions.getSpirvValidatorOptions()));
buildSpirvAsmTasks.pushBack(BuildSpirVAsmTask(progIter.getProgram(), &programs.back()));
buildSpirvAsmTasks.back().setCommandline(testCtx.getCommandLine());
executor.submit(&buildSpirvAsmTasks.back());
}
}
iterator.next();
}
}
// Need to wait until tasks completed before freeing task memory
executor.waitForComplete();
}
if (validateBinaries)
{
std::vector<ValidateBinaryTask> validationTasks;
validationTasks.reserve(programs.size());
for (de::PoolArray<Program>::iterator progIter = programs.begin(); progIter != programs.end(); ++progIter)
{
if (progIter->buildStatus == Program::STATUS_PASSED)
{
validationTasks.push_back(ValidateBinaryTask(&*progIter));
executor.submit(&validationTasks.back());
}
}
executor.waitForComplete();
}
{
vk::BinaryRegistryWriter registryWriter (dstPath);
for (de::PoolArray<Program>::iterator progIter = programs.begin(); progIter != programs.end(); ++progIter)
{
if (progIter->buildStatus == Program::STATUS_PASSED)
registryWriter.addProgram(progIter->id, *progIter->binary);
}
registryWriter.write();
}
{
BuildStats stats;
stats.notSupported = notSupported;
for (de::PoolArray<Program>::iterator progIter = programs.begin(); progIter != programs.end(); ++progIter)
{
const bool buildOk = progIter->buildStatus == Program::STATUS_PASSED;
const bool validationOk = progIter->validationStatus != Program::STATUS_FAILED;
if (buildOk && validationOk)
stats.numSucceeded += 1;
else
{
stats.numFailed += 1;
tcu::print("ERROR: %s / %s: %s failed\n",
progIter->id.testCasePath.c_str(),
progIter->id.programName.c_str(),
(buildOk ? "validation" : "build"));
tcu::print("%s\n", (buildOk ? progIter->validationLog.c_str() : progIter->buildLog.c_str()));
}
}
return stats;
}
}
} // vkt
namespace opt
{
DE_DECLARE_COMMAND_LINE_OPT(DstPath, std::string);
DE_DECLARE_COMMAND_LINE_OPT(Cases, std::string);
DE_DECLARE_COMMAND_LINE_OPT(Validate, bool);
DE_DECLARE_COMMAND_LINE_OPT(VulkanVersion, deUint32);
DE_DECLARE_COMMAND_LINE_OPT(ShaderCache, bool);
DE_DECLARE_COMMAND_LINE_OPT(ShaderCacheFilename, std::string);
DE_DECLARE_COMMAND_LINE_OPT(ShaderCacheTruncate, bool);
DE_DECLARE_COMMAND_LINE_OPT(SpirvOptimize, bool);
DE_DECLARE_COMMAND_LINE_OPT(SpirvOptimizationRecipe,std::string);
DE_DECLARE_COMMAND_LINE_OPT(SpirvAllow14, bool);
static const de::cmdline::NamedValue<bool> s_enableNames[] =
{
{ "enable", true },
{ "disable", false }
};
void registerOptions (de::cmdline::Parser& parser)
{
using de::cmdline::Option;
using de::cmdline::NamedValue;
static const NamedValue<deUint32> s_vulkanVersion[] =
{
{ "1.0", VK_MAKE_VERSION(1, 0, 0) },
{ "1.1", VK_MAKE_VERSION(1, 1, 0) },
};
DE_STATIC_ASSERT(vk::SPIRV_VERSION_1_4 + 1 == vk::SPIRV_VERSION_LAST);
parser << Option<opt::DstPath>("d", "dst-path", "Destination path", "out")
<< Option<opt::Cases>("n", "deqp-case", "Case path filter (works as in test binaries)")
<< Option<opt::Validate>("v", "validate-spv", "Validate generated SPIR-V binaries")
<< Option<opt::VulkanVersion>("t", "target-vulkan-version", "Target Vulkan version", s_vulkanVersion, "1.1")
<< Option<opt::ShaderCache>("s", "shadercache", "Enable or disable shader cache", s_enableNames, "enable")
<< Option<opt::ShaderCacheFilename>("r", "shadercache-filename", "Write shader cache to given file", "shadercache.bin")
<< Option<opt::ShaderCacheTruncate>("x", "shadercache-truncate", "Truncate shader cache before running", s_enableNames, "enable")
<< Option<opt::SpirvOptimize>("o", "deqp-optimize-spirv", "Enable optimization for SPIR-V", s_enableNames, "disable")
<< Option<opt::SpirvOptimizationRecipe>("p","deqp-optimization-recipe", "Shader optimization recipe")
<< Option<opt::SpirvAllow14>("e","allow-spirv-14", "Allow SPIR-V 1.4 with Vulkan 1.1");
}
} // opt
int main (int argc, const char* argv[])
{
de::cmdline::CommandLine cmdLine;
tcu::CommandLine deqpCmdLine;
{
de::cmdline::Parser parser;
opt::registerOptions(parser);
if (!parser.parse(argc, argv, &cmdLine, std::cerr))
{
parser.help(std::cout);
return -1;
}
}
{
vector<const char*> deqpArgv;
deqpArgv.push_back("unused");
if (cmdLine.hasOption<opt::Cases>())
{
deqpArgv.push_back("--deqp-case");
deqpArgv.push_back(cmdLine.getOption<opt::Cases>().c_str());
}
if (cmdLine.hasOption<opt::ShaderCacheFilename>())
{
deqpArgv.push_back("--deqp-shadercache-filename");
deqpArgv.push_back(cmdLine.getOption<opt::ShaderCacheFilename>().c_str());
}
if (cmdLine.hasOption<opt::ShaderCache>())
{
deqpArgv.push_back("--deqp-shadercache");
if (cmdLine.getOption<opt::ShaderCache>())
deqpArgv.push_back("enable");
else
deqpArgv.push_back("disable");
}
if (cmdLine.hasOption<opt::ShaderCacheTruncate>())
{
deqpArgv.push_back("--deqp-shadercache-truncate");
if (cmdLine.getOption<opt::ShaderCacheTruncate>())
deqpArgv.push_back("enable");
else
deqpArgv.push_back("disable");
}
if (cmdLine.hasOption<opt::SpirvOptimize>())
{
deqpArgv.push_back("--deqp-optimize-spirv");
if (cmdLine.getOption<opt::SpirvOptimize>())
deqpArgv.push_back("enable");
else
deqpArgv.push_back("disable");
}
if (cmdLine.hasOption<opt::SpirvOptimizationRecipe>())
{
deqpArgv.push_back("--deqp-optimization-recipe");
deqpArgv.push_back(cmdLine.getOption<opt::SpirvOptimizationRecipe>().c_str());
}
if (!deqpCmdLine.parse((int)deqpArgv.size(), &deqpArgv[0]))
return -1;
}
try
{
tcu::DirArchive archive (".");
tcu::TestLog log (deqpCmdLine.getLogFileName(), (argc - 1), (char **)(argv + 1), deqpCmdLine.getLogFlags());
tcu::Platform platform;
tcu::TestContext testCtx (platform, archive, log, deqpCmdLine, DE_NULL);
vk::SpirvVersion baselineSpirvVersion = vk::getBaselineSpirvVersion(cmdLine.getOption<opt::VulkanVersion>());
vk::SpirvVersion maxSpirvVersion = vk::getMaxSpirvVersionForGlsl(cmdLine.getOption<opt::VulkanVersion>());
tcu::print("SPIR-V versions: baseline: %s, max supported: %s\n",
getSpirvVersionName(baselineSpirvVersion).c_str(),
getSpirvVersionName(maxSpirvVersion).c_str());
const vkt::BuildStats stats = vkt::buildPrograms(testCtx,
cmdLine.getOption<opt::DstPath>(),
cmdLine.getOption<opt::Validate>(),
cmdLine.getOption<opt::VulkanVersion>(),
baselineSpirvVersion,
maxSpirvVersion,
cmdLine.getOption<opt::SpirvAllow14>());
tcu::print("DONE: %d passed, %d failed, %d not supported\n", stats.numSucceeded, stats.numFailed, stats.notSupported);
return stats.numFailed == 0 ? 0 : -1;
}
catch (const std::exception& e)
{
tcu::die("%s", e.what());
}
}