blob: c80ae877e83f64e8830f8f440f807d9603a16c7a [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;
explicit Program (const vk::ProgramIdentifier& id_)
: id (id_)
, buildStatus (STATUS_NOT_COMPLETED)
, validationStatus (STATUS_NOT_COMPLETED)
{}
Program (void)
: id ("", "")
, buildStatus (STATUS_NOT_COMPLETED)
, validationStatus (STATUS_NOT_COMPLETED)
{}
};
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";
}
class BuildGlslTask : public Task
{
public:
BuildGlslTask (const vk::GlslSource& source, Program* program)
: m_source (source)
, m_program (program)
{}
BuildGlslTask (void) : m_program(DE_NULL) {}
void execute (void)
{
glu::ShaderProgramInfo buildInfo;
try
{
m_program->binary = ProgramBinarySp(vk::buildProgram(m_source, &buildInfo));
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::GlslSource m_source;
Program* m_program;
};
void writeBuildLogs (const vk::SpirVProgramInfo& buildInfo, std::ostream& dst)
{
dst << "source:\n"
<< "---\n"
<< buildInfo.source << "\n"
<< "---\n";
}
class BuildSpirVAsmTask : public Task
{
public:
BuildSpirVAsmTask (const vk::SpirVAsmSource& source, Program* program)
: m_source (source)
, m_program (program)
{}
BuildSpirVAsmTask (void) : m_program(DE_NULL) {}
void execute (void)
{
vk::SpirVProgramInfo buildInfo;
try
{
m_program->binary = ProgramBinarySp(vk::assembleProgram(m_source, &buildInfo));
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;
};
class ValidateBinaryTask : public Task
{
public:
ValidateBinaryTask (Program* program)
: m_program(program)
{}
void execute (void)
{
DE_ASSERT(m_program->buildStatus == Program::STATUS_PASSED);
std::ostringstream validationLog;
if (vk::validateProgram(*m_program->binary, &validationLog))
m_program->validationStatus = Program::STATUS_PASSED;
else
m_program->validationStatus = Program::STATUS_FAILED;
}
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;
BuildStats (void)
: numSucceeded (0)
, numFailed (0)
{
}
};
BuildStats buildPrograms (tcu::TestContext& testCtx, const std::string& dstPath, bool validateBinaries)
{
const deUint32 numThreads = deGetNumAvailableLogicalCores();
TaskExecutor executor (numThreads);
// de::PoolArray<> is faster to build than std::vector
de::MemPool programPool;
de::PoolArray<Program> programs (&programPool);
{
de::MemPool tmpPool;
de::PoolArray<BuildGlslTask> buildGlslTasks (&tmpPool);
de::PoolArray<BuildSpirVAsmTask> buildSpirvAsmTasks (&tmpPool);
// Collect build tasks
{
const UniquePtr<tcu::TestPackageRoot> root (createRoot(testCtx));
tcu::DefaultHierarchyInflater inflater (testCtx);
tcu::TestHierarchyIterator iterator (*root, inflater, testCtx.getCommandLine());
while (iterator.getState() != tcu::TestHierarchyIterator::STATE_FINISHED)
{
if (iterator.getState() == tcu::TestHierarchyIterator::STATE_ENTER_NODE &&
tcu::isTestNodeTypeExecutable(iterator.getNode()->getNodeType()))
{
const TestCase* const testCase = dynamic_cast<TestCase*>(iterator.getNode());
const string casePath = iterator.getNodePath();
vk::SourceCollections sourcePrograms;
testCase->initPrograms(sourcePrograms);
for (vk::GlslSourceCollection::Iterator progIter = sourcePrograms.glslSources.begin();
progIter != sourcePrograms.glslSources.end();
++progIter)
{
programs.pushBack(Program(vk::ProgramIdentifier(casePath, progIter.getName())));
buildGlslTasks.pushBack(BuildGlslTask(progIter.getProgram(), &programs.back()));
executor.submit(&buildGlslTasks.back());
}
for (vk::SpirVAsmCollection::Iterator progIter = sourcePrograms.spirvAsmSources.begin();
progIter != sourcePrograms.spirvAsmSources.end();
++progIter)
{
programs.pushBack(Program(vk::ProgramIdentifier(casePath, progIter.getName())));
buildSpirvAsmTasks.pushBack(BuildSpirVAsmTask(progIter.getProgram(), &programs.back()));
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;
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);
} // opt
void registerOptions (de::cmdline::Parser& parser)
{
using de::cmdline::Option;
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");
}
int main (int argc, const char* argv[])
{
de::cmdline::CommandLine cmdLine;
tcu::CommandLine deqpCmdLine;
{
de::cmdline::Parser parser;
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 (!deqpCmdLine.parse((int)deqpArgv.size(), &deqpArgv[0]))
return -1;
}
try
{
tcu::DirArchive archive (".");
tcu::TestLog log (deqpCmdLine.getLogFileName(), deqpCmdLine.getLogFlags());
tcu::Platform platform;
tcu::TestContext testCtx (platform, archive, log, deqpCmdLine, DE_NULL);
const vkt::BuildStats stats = vkt::buildPrograms(testCtx,
cmdLine.getOption<opt::DstPath>(),
cmdLine.getOption<opt::Validate>());
tcu::print("DONE: %d passed, %d failed\n", stats.numSucceeded, stats.numFailed);
return stats.numFailed == 0 ? 0 : -1;
}
catch (const std::exception& e)
{
tcu::die("%s", e.what());
}
}